Wire protocol
Every message is a single JSON object with a type field. Over WebSocket, send one message per text frame. Over UDP, send one message per datagram.
Client to server
join
{ "type": "join", "roomId": "lobby-1", "playerId": "alice", "apiKey": "ns_api_..." }
apiKey identifies the project for UDP clients. WebSocket clients are identified by the subdomain they connect to.
leave
{ "type": "leave" }
call
{ "type": "call", "callId": "1", "function": "useItem", "args": { "itemId": "potion" } }
callId is any string you choose. The matching result echoes it.
ping
{ "type": "ping" }
UDP clients must ping at least every 20 seconds.
Server to client
joined
{
"type": "joined",
"roomId": "lobby-1",
"playerId": "alice",
"session": "...",
"room": { "roomId": "lobby-1", "properties": {}, "players": [{ "playerId": "alice", "properties": {} }] }
}
result
{ "type": "result", "callId": "1", "ok": true, "result": { "remaining": 1 } }
{ "type": "result", "callId": "1", "ok": false, "error": { "code": "ITEM_NOT_OWNED", "message": "You do not own that item." } }
event
{ "type": "event", "name": "itemUsed", "data": { "playerId": "alice", "itemId": "potion" } }
property and propertyDelete
{ "type": "property", "scope": "player", "playerId": "alice", "key": "inventory", "value": ["sword"] }
{ "type": "propertyDelete", "scope": "room", "key": "round" }
scope is player or room. Global properties are delivered as room properties.
left, pong and error
{ "type": "left" }
{ "type": "pong" }
{ "type": "error", "code": "NOT_JOINED", "message": "Join a room first." }