Messages
The PairKit wire protocol is a discriminated union of JSON messages with a stable type field. Current protocol version: 1.
The TypeScript schema in messages.ts is the source of truth. C# types under sdks/unity/Runtime/Protocol/ are generated from it; never hand-edited.
Client → Server
Originated by either a host (Unity) or a controller (phone), bound for the relay.
| Type | Description |
|---|---|
session.create | Host → relay: open a new session and receive a short code. Optionally carries a flow for multi-screen experiences, or resume_code + resume_host_id to reattach to an existing session after a relay restart. |
session.join | Controller → relay: join an existing session by code. Optionally carries resume_player_id for transparent reconnect within the grace window. |
session.leave | Either side → relay: clean disconnect. Optional reason field. |
control.send_to_player | Host → relay: deliver a custom event to one player. The relay forwards it as control.event. |
control.broadcast | Host → relay: deliver a custom event to every player in the session. ACK-tracked variants exist for delivery confirmation. |
control.ack | Controller → host: ACK that a control.broadcast was received. Drives the BroadcastResult returned by BroadcastToAllPlayersAsync. |
screen.query | Controller → relay: ask for the current screen state. Fallback when the controller’s local view diverged after a reconnect race. |
Server → Client
Originated by the relay, bound for either side.
| Type | Description |
|---|---|
session.created | Relay → host: session was allocated. Carries the 4-character code, the secret host_id (treat as a bearer token), and relay_capabilities (e.g. strict_payloads). |
session.joined | Relay → controller: join succeeded. Carries the assigned player_id, the layout/flow to render, and any current screen state for resume. |
player.join | Relay → host: a controller joined the session. |
player.leave | Relay → host: a controller left. reason ∈ user_left |
player.update | Relay → host: a player’s display name or color changed mid-session. |
control.event | Relay → controller: a host-originated control event (e.g. vibrate, flash). Routed to session-level handlers or per-widget onControl callbacks. |
control.broadcast_result | Relay → host: aggregated delivery result (Delivered / Missed / TimedOut) for an ACK-tracked control.broadcast. |
screen.state | Relay → controller: response to screen.query. |
system.error | Relay → either side: error report with a stable code (see Errors) and a human-readable message. |
Either direction
These messages flow in both directions (e.g. the relay forwards a host’s broadcast to controllers using the same envelope).
| Type | Description |
|---|---|
input.widget_event | Controller → host (forwarded by relay): a widget emitted an event. Carries widget_id, event_type, and an arbitrary payload shaped per widget. |
input.response | Controller → host: response to a control.request (PromptAsync / RequestAsync). Carries the request id and the response payload. |
control.request | Host → relay → controller: ask a single player for one response (powers PromptAsync / RequestAsync). |
control.request_cancel | Either side: cancel an outstanding control.request. |
control.resend_request | Host → controller: ask the phone to replay missing stroke_progress events for a drawing_canvas (powers RequestResendAsync). |
control.resend_failed | Controller → host: phone cannot replay (buffer expired, widget not ready, …). Drives the Rejected outcome of RequestResendAsync. |
control.resend_accepted | Controller → host: phone is replaying. Carries replayed_count. Drives the Accepted outcome of RequestResendAsync. |
screen.show | Host → relay → controllers: move targeted players to a named screen. revision is monotonic per-(session, player) globally. |
screen.update_data | Host → relay → controllers: shallow-merge a patch into a player’s current screen data. Used to update bound values like {score} without changing the screen. |
screen.acknowledge | Controller → host: ACK that a screen transition was applied locally. Carries the screen name and revision so the host can detect dropped transitions. |
system.ping | Either side: keepalive + clock-sync. Sender stamps sent_at (unix ms); receiver echoes it in system.pong. |
system.pong | Either side: response to system.ping. The SDK uses (host_recv − pong_send) to estimate clock skew for timer widgets. |
Adding a new message
- Add the interface to
messages.tsand a new entry inMESSAGE_TYPES. - Add it to the appropriate union (
ClientToServerMessage/ServerToClientMessage). - Run
pnpm --filter @pairkit/shared-protocol generate:csharpto regenerate Unity types. - Add a description to
MESSAGE_DESCRIPTIONSinscripts/generate-docs.tsand runpnpm --filter @pairkit/shared-protocol generate:docs.
CI fails any PR that adds a message without all three steps.