Wire Protocol
Every message over the socket is a binary frame:
version(1 byte)type(1 byte)headerLen(2 bytes, big-endian)payloadLen(3 bytes, big-endian)header(headerLenbytes of UTF-8 JSON)payload(payloadLenbytes, optional)
MessageProtocol in core/messages implements encode/decode with validation:
headers are capped at MAX_HEADER_BYTES (0xffff) and frames at
MAX_FRAME_BYTES (16 MiB); an option can lower the frame cap, never raise it.
The same limits are mirrored by the Kotlin and Swift hosts. VERSION is
pinned at 1 by the contract test — changing it is a major-version event.
Message types
Section titled “Message types”DISPATCH— fire-and-forget.INVOKE_REQUEST— request/response; echoesrequestId.INVOKE_RESPONSE— the reply to anINVOKE_REQUEST, carrying the samerequestIdplus a result or a deterministicErrorCode.HOST_CAPABILITIES_*,HOST_INVOKE_*— worklet ↔ host only. They are consumed from the host IPC channel; aHOST_*_RESPONSEsent over the WebSocket is ignored, so a page cannot answer a host call.
Decoding is an allowlist
Section titled “Decoding is an allowlist”A decoded header is rebuilt from the known fields of its type: unknown
fields are dropped, and args/result objects are rebuilt without a
prototype (__proto__, constructor and prototype keys are removed).
pluginId, event and requestId are at most 256 UTF-8 bytes, and the header
must be valid UTF-8 JSON.
Bad frames
Section titled “Bad frames”- A frame that is correctly delimited but whose content is invalid (not
UTF-8, not JSON, an unknown header type, an over-long id) is skipped on its
own; the stream stays in sync. An
INVOKE_REQUESTamong them is answered withFRAME_INVALIDwhen itsrequestIdsurvived. On the host IPC, a bad host response fails the call waiting on it at once. - A framing violation (wrong version, a length past the caps) means the stream can no longer be trusted. The WebSocket is closed (the page reconnects on a clean stream); the host IPC drops the buffered bytes and gives up on the channel after repeated failures. Frames decoded before the violation are still handled.
Minimal example
Section titled “Minimal example”import { MessageProtocol, MessageType, type MessageHeader,} from '@ekrooh/bare/core';
const protocol = new MessageProtocol();
const header: MessageHeader = { type: 'DISPATCH', pluginId: 'core.health', event: 'health.ping',};
const frame = protocol.encode(MessageType.ENVELOPE, header);const message = protocol.decode(frame);The codec pools buffers, so callers slice a standalone copy before handing a frame to the socket.