Transports
A MessageTransport is the UI-facing boundary. It has send(type, header, payload?), subscribe(handler) and an optional close(), and it does not
know about worklets, Bare, or host IPC. A page does not call plugins on the
transport directly: it wraps it in a protocol messenger and a plugin bus.
import { MessageType, createPluginBus, createProtocolMessenger,} from '@ekrooh/bare/core';import { healthEvents } from '@ekrooh/bare/plugins/health/events';import { createWebSocketTransport } from '@ekrooh/bare/transports';
const transport = createWebSocketTransport();const messenger = createProtocolMessenger((request, payload) => { transport.send(MessageType.ENVELOPE, request, payload);});transport.subscribe((message) => messenger.handleIncoming(message.header));const bus = createPluginBus(messenger);
const [err, pong] = await bus.invoke(healthEvents.health.ping('hello'));WebSocket transport
Section titled “WebSocket transport”- Same-origin default: on-device the page is served by the loopback server,
so it connects to
ws://<location.host>— no mixed content, no custom scheme. - Cookie bootstrap: when the shell injected
window.__ekrooh = { bootstrap }(a one-time nonce — never the raw token), the transport exchanges it for abare_session_<port>cookie viaPOST /loginbefore opening the socket. - Reconnect: backoff from 250 ms doubling to a 2 s cap, with equal jitter
(half fixed, half random) so many WebViews do not reconnect in lockstep.
- A client that has never connected gives up after 5 retries, but only
once at least 10 s (
minBootWindowMs) have passed since its first attempt. - Once it has connected, a lost socket is retried forever at the capped backoff. The retry count resets only after a socket has stayed open for 5 s, so a socket that opens and drops at once keeps backing off.
- A socket stuck connecting is dropped after 10 s (
openTimeoutMs). - Returning to the foreground (
WAKE) retries at once, and also recovers a client that had given up. - When the server replaces this socket because another document took the
session (close code 4001,
"replaced"), the client does not reconnect at once: it waits 30 s or until the page returns to the foreground, so two documents cannot evict each other forever.
- A client that has never connected gives up after 5 retries, but only
once at least 10 s (
- Failing fast: invokes already sent fail with
TRANSPORT_ERRORas soon as their socket is lost (they no longer wait out their timeout). While connecting, up to 256 requests (maxQueued) are held; past that, new requests fail at once. close()drops the socket for good, stops reconnecting, and fails every pending and later request withTRANSPORT_ERROR.diagnostics: truefolds the server’s recent[loopback]rejection log lines into the give-up error text. It is off by default: log lines are for a diagnostic harness, not for a shipped UI. Pass the same flag tocreateProtocolMessengerto get them on invoke timeouts too. The reference apps turn it on only in dev or when built withVITE_BARE_DIAGNOSTICS=true(the device CI jobs do).- No
?token=URL fallback — a credential is never placed in a URL. - Browser dev is cross-origin — pass an explicit URL (
VITE_BARE_WS_URL).
The connection lifecycle is modeled as a mantaq Actor (see
State Machines) — internal-only, never part
of the public surface.
Mock transport
Section titled “Mock transport”createMockTransport() is deterministic and in-process, used by unit and e2e
tests and browser dev. VITE_TRANSPORT_MODE=mock selects it in the reference
app. It routes requests through the real plugin kernel and the real
core.health, core.discovery, core.logs and core.permissions manifests,
so argument checks, unsupported-event errors and discovery match the worklet.
Only the host is stubbed: known permission ids are granted (unknown or missing
ids are unsupported), and vendor.media returns a fixed data: image after
the same kind check as the real plugin. It never reconnects. It warns once if
it runs inside an app shell. It ships inside the @ekrooh/bare/transports
bundle, so it is present in a production build even when unused; select it
only behind a dev/test guard (the reference app refuses
VITE_TRANSPORT_MODE=mock in a production build).