Skip to content

Protocol

Zocket uses a JSON-based protocol over standard WebSockets. All messages are plain JSON objects with a type discriminant.

const MSG = {
RPC: "rpc",
RPC_RESULT: "rpc:result",
EVENT: "event",
EVENT_SUB: "event:sub",
EVENT_UNSUB: "event:unsub",
STATE_SUB: "state:sub",
STATE_UNSUB: "state:unsub",
STATE_SNAPSHOT: "state:snapshot",
STATE_PATCH: "state:patch",
};

Invoke a method on an actor instance:

{
"type": "rpc",
"id": "rpc_1_m3abc",
"actor": "chat",
"actorId": "room-1",
"method": "sendMessage",
"input": { "text": "hello" }
}

The id is generated by the client (rpc_{counter}_{timestamp}) and used to correlate the response.

{ "type": "event:sub", "actor": "chat", "actorId": "room-1" }
{ "type": "event:unsub", "actor": "chat", "actorId": "room-1" }
{ "type": "state:sub", "actor": "chat", "actorId": "room-1" }
{ "type": "state:unsub", "actor": "chat", "actorId": "room-1" }

Subscribing to state triggers an immediate state:snapshot response.

{
"type": "rpc:result",
"id": "rpc_1_m3abc",
"result": { "count": 42 }
}

On error:

{
"type": "rpc:result",
"id": "rpc_1_m3abc",
"error": "Unauthorized"
}

Broadcast to all event subscribers for this actor instance:

{
"type": "event",
"actor": "chat",
"actorId": "room-1",
"event": "newMessage",
"payload": { "text": "hello", "from": "conn_123" }
}

Full state sent when a client first subscribes:

{
"type": "state:snapshot",
"actor": "chat",
"actorId": "room-1",
"state": { "messages": [], "online": ["conn_123"] }
}

Incremental update using JSON Patch (RFC 6902):

{
"type": "state:patch",
"actor": "chat",
"actorId": "room-1",
"patches": [
{ "op": "add", "path": "/messages/-", "value": { "text": "hi" } }
]
}

Zocket generates three patch operations from Immer diffs:

OperationDescription
addAdd a value at a path
replaceReplace value at a path
removeRemove value at a path

Paths follow RFC 6902 pointer syntax: /messages/0/text, /players/- (append).

The @zocket/core package exports builder functions for constructing messages:

import { rpcCall, rpcResult, event, stateSub, stateSnapshot, statePatch } from "@zocket/core";
// Client builds:
const msg = rpcCall("chat", "room-1", "sendMessage", { text: "hi" });
// Server builds:
const result = rpcResult(msg.id, { ok: true });
const snap = stateSnapshot("chat", "room-1", currentState);
const patch = statePatch("chat", "room-1", [{ op: "add", path: "/count", value: 1 }]);
import { parseMessage } from "@zocket/core";
const msg = parseMessage(rawString);
// Returns typed ClientMessage | ServerMessage | null

Returns null for invalid JSON or messages missing a type field.