NetStack

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." }