Source of truth. This page mirrors state-server/docs/JS/realtime-websocket.md. The repo is canonical — update there first, then resync here.
The SDK uses WebSocket Protocol v2.0 for real-time state delivery. The protocol is CBOR-encoded and supports two delivery modes: snapshot streaming and version-batch replay / live updates.
await sdk.connectWebSocket(); // usually via sdk.initialize
sdk.isConnected(); // boolean
sdk.disconnectWebSocket();
sdk.destroy();
The WebSocket token is synced from the auth service before connecting. If authenticated, the JWT is passed as a query parameter: wss://<host>/ws?token=<jwt>.
Auto-reconnect is on by default. On disconnect the SDK waits reconnectDelay ms, then doubles up to maxReconnectDelay.
2.0.wss://<host>/ws?token=<jwt>.Message envelope:
{
id: "msg_123",
type: "response",
version: "2.0",
timestamp: 1710000000,
payload: { ... },
metadata: { ... },
}
import { WebSocketState } from '@state-server/sdk';
WebSocketState.CONNECTING // 0
WebSocketState.OPEN // 1
WebSocketState.CLOSING // 2
WebSocketState.CLOSED // 3
const response = await sdk.joinBrane(braneId);
const response2 = await sdk.joinBrane(braneId, lastKnownVersion); // resume
await sdk.leaveBrane(braneId);
Lifecycle (emitted as events):
response:accepted — server accepted the join.entity:snapshot — entity snapshots delivered one at a time.response:snapshot_complete — all snapshot entities delivered.entity:update — version-batch updates (catching up).response:version_complete — a version batch finished.response:live_ready — caught up; future updates are live.response:leave_complete — brane left successfully.One active brane per socket — joining a new brane cancels the current one.
Resume mode: when resumeVersion is provided, the server skips the snapshot and sends only updates from that version forward: accepted(mode="resume") → entity.update* → version_complete → live_ready.
const r1 = await sdk.getGroup(groupId); // group hierarchy
const r2 = await sdk.getObject(objectId); // single object
const r3 = await sdk.getPrototype(prototypeId);
No live mode — these are one-time snapshots.
sdk.on('ws:entity:snapshot', (event) => {
// event.id, event.payload (Block|Group|BlockPrototype|GroupPrototype)
// event.metadata: { version, entity_id, root_id }
});
sdk.on('ws:entity:update', (event) => {
// event.payload: updated entity or null (for remove/delete)
// event.metadata: { version, entity_id, action, cause, command_id }
// action: 'created' | 'updated' | 'removed' | 'deleted'
// cause: 'normal' | 'undo' | 'redo'
});
const subId = await sdk.subscribeToEntity(entityId, (event) => {
console.log(event.event_type, event.object_id, event.object, event.changed_fields);
});
await sdk.unsubscribeFromEntity(entityId, subId);
Low-level:
const subId = await sdk.ws.subscribe('object', [entityId], ['updated', 'deleted'], handler);
await sdk.ws.unsubscribe(subId);
await sdk.ws.createObject({ type: 'block', name: { en: 'New Block' }, parent_id: braneId });
await sdk.ws.updateObject(objectId, { properties: { color: '#ff0000' } });
await sdk.ws.deleteObject(objectId);
await sdk.ws.takeObject(objectId);
await sdk.ws.putObject(objectId, braneId, { transform: matrix });
await sdk.ws.createPrototype({ type: 'block', name: { en: 'New Proto' } });
await sdk.ws.updatePrototype(protoId, { public: true });
await sdk.ws.deletePrototype(protoId);
await sdk.ws.instantiatePrototype(protoId, { parent_id: braneId });
Mutations are acknowledged with response(stage="accepted") carrying resulting_version and command_id. State changes are then delivered via entity.update.
const result = await sdk.undoCommand(braneId);
// result.command_id, result.target_command_id, result.resulting_version
const result2 = await sdk.redoCommand(braneId);
Undo / redo produce new versions; their entity.update events carry cause: 'undo' / cause: 'redo'.
const sdk = new StateServerSDK({
baseUrl: 'https://example.com',
loadLastAppliedVersion: async (braneId) => {
return parseInt(localStorage.getItem(`version_${braneId}`)) || null;
},
saveLastAppliedVersion: async (braneId, version) => {
localStorage.setItem(`version_${braneId}`, String(version));
},
});
Versions are persisted automatically at snapshot_complete and version_complete checkpoints.
System. system.welcome, system.ping / system.pong, system.error, system.notification.
Response stages. accepted, snapshot_complete, version_complete, live_ready, leave_complete.
Entity. entity.snapshot, entity.update.
Object ops (outbound). object.create, object.get, object.update, object.delete, object.take, object.put, object.can_put.
Prototype ops (outbound). prototype.create, prototype.get, prototype.update, prototype.delete, prototype.instantiate, prototype.can_instantiate.
Brane ops (outbound). brane.join, brane.leave.
Other (outbound). group.get, user.state, subscription.create, subscription.delete, command.undo, command.redo.
Event messages (inbound). event.object.created, event.object.updated, event.object.deleted, event.presence.
await sdk.ws.send(type, payload, options);
const response = await sdk.ws.request(type, payload, options);
await sdk.ws.ping();
| Event | Payload | Description |
|---|---|---|
ws:connected |
— | Connected. |
ws:disconnected |
{code, reason} |
Disconnected. |
ws:reconnecting |
{attempt} |
Reconnecting. |
ws:reconnected |
— | Reconnected. |
ws:error |
Error |
Connection error. |
ws:message |
WebSocketMessage |
Any incoming message. |
ws:pong |
{serverTime} |
Pong response. |
ws:response:accepted |
{id, payload, metadata} |
Request accepted. |
ws:response:snapshot_complete |
… | Snapshot done. |
ws:response:version_complete |
… | Version batch done. |
ws:response:live_ready |
… | Live mode ready. |
ws:response:leave_complete |
… | Left brane. |
ws:entity:snapshot |
WebSocketEntitySnapshotEvent |
Entity snapshot. |
ws:entity:update |
WebSocketEntityUpdateEvent |
Entity update. |
ws:subscribed |
{subscriptionId} |
Subscription created. |
ws:unsubscribed |
{subscriptionId} |
Subscription deleted. |
Objects, WebSocket protocol v2.0. state-server/docs/JS/events-reference.md and state-server/docs/JS/configuration.md in-repo (mirror pending).