PacketRelayDocs

Protocol reference#

PacketRelay selects a session protocol from the first WebSocket text frame. URL paths do not select bridge, broadcast, or room behaviour. After admission, the relay forwards supported text traffic according to the chosen session kind.

Before you implement a client#

  • Use wss:// outside a trusted local network.
  • Send the hello within five seconds unless the operator configured another deadline.
  • Keep identifiers within the configured byte limit and use only [A-Za-z0-9._-].
  • Keep frames within the configured message-size ceiling.
  • Answer WebSocket pings, and handle the relay-initiated close codes below.

WebSocket Ping and Pong frames sent before the hello are ignored rather than treated as a malformed hello, so a client may keep a socket warm while it prepares its first envelope. The hello deadline still applies to the whole sequence.

Client hellos#

Type Required fields Purpose
desktop_hello session_id Create or occupy the desktop side of a bridge.
mobile_hello session_id Join the mobile side of an existing bridge.
host_hello session_id Create or occupy the host side of a broadcast.
spectator_hello session_id Join a broadcast as a spectator.
room_hello session_id, member_id, pubkey, nonce, sig Create or join a signed room.

Example bridge hello:

{"type":"desktop_hello","session_id":"demo-session"}

Extra JSON fields are ignored. A client must therefore negotiate application versions after admission instead of assuming an extra hello field was accepted.

Relay responses#

Type Fields Meaning
session_ready session_id Bridge or broadcast admission succeeded.
peer_connected — The other bridge role is present.
peer_disconnected — The other bridge role left.
host_connected — A broadcast host is present.
host_disconnected — The broadcast host left.
viewer_count count Current broadcast spectator count.
room_ready session_id Signed room admission succeeded.
member_joined member_id A room member joined or appears in the initial roster.
member_left member_id A room member left.
member_count count Current room membership.
member_frame member_id, payload Attributed room payload.
error message Admission or session operation was refused.

All relay envelopes are WebSocket text frames. Binary frames are not an application transport. WebSocket Ping receives Pong.

The application-level text envelope {"type":"ping"} is consumed as a keepalive on bridge and room sessions: it is not forwarded to the peer or to other members. A broadcast host is the exception — the relay fans that envelope out to every spectator like any other payload. Spectator clients must therefore be able to receive a ping envelope, and a host that wants a keepalive that no spectator sees should use a WebSocket Ping frame instead.

Room frame size#

Bridge and broadcast frames are forwarded as sent. A room frame is wrapped in a member_frame envelope stamped with the relay-authenticated sender, which adds 51 fixed bytes plus the sender's member_id. JSON escaping expands the payload further: each " or \ becomes two bytes, backspace, tab, newline, form feed and carriage return become two bytes each, and every other control character becomes six.

The usable room payload is therefore smaller than the configured message-size ceiling. With the default 64 KiB ceiling and a 64-byte member_id, an unescaped payload may be at most 65,421 bytes.

A room frame whose serialised envelope would exceed the ceiling is not fanned out. The sender stays connected and receives:

{"type":"error","message":"room frame exceeds message ceiling after envelope"}

Treat this as a recoverable client error: chunk the payload and resend. Sizing room payloads against the raw ceiling rather than the usable figure is the usual cause.

Heartbeats#

After 25 seconds with no inbound frame the relay sends a WebSocket ping and expects any frame back within 10 seconds. Both intervals are configurable — see Configuration. Any inbound frame counts, not only a Pong, so a client that is already sending traffic is never pinged.

A client that answers pings automatically, as most WebSocket libraries do, needs no change. A client that suppresses automatic pongs must send something within the pong deadline or it will be closed.

Close codes#

The relay closes with one of these codes. Each wants a different client response, so do not collapse them into one reconnect path.

Code Reason text Meaning Client response
1001 relay shutting down The relay is draining before it stops. Reconnect after a short backoff; the endpoint returns.
1008 message budget exceeded The connection passed its message or byte budget. Reduce the send rate before reconnecting.
4000 replaced by a newer connection A newer connection took this slot. Do not reconnect automatically; another client owns the session.
4001 heartbeat timeout No frame arrived within the pong deadline. Reconnect normally and repeat admission.
4002 slow consumer The client stopped draining its outbound queue. Reconnect, then read faster or reduce the frame rate.

Peers that close first, and transports that fail outright, produce no relay close frame. The relay never emits 1005, 1006, or 1015.

Before a 1001 close the relay still sends the normal peer_disconnected, host_disconnected, or member_left notification to the remaining participants, so a session teardown looks the same to them as an ordinary disconnect.

Reserved paths#

WebSocket route selection does not depend on the URL path, but some paths are not available to clients. /health, /healthz, /ready, /readyz, and /metrics serve HTTP, and any other path beginning with /health, /ready, or /metrics is refused with 404. The paths /ws/host, /ws/device, /api, /auth, and /push, including anything under /api/, /auth/, or /push/, are reserved and also answer 404. Connect clients to any remaining path, such as /.

Verify#

  1. Complete the full First bridge sequence.
  2. Confirm a malformed identifier receives an error envelope.
  3. Confirm a binary hello is refused.
  4. Confirm a normal payload is forwarded without transformation.
  5. Confirm the client stays connected and idle for at least a minute, proving it answers the relay's heartbeat ping.
  6. Confirm each close code in the table is handled distinctly, in particular that 4000 does not trigger an automatic reconnect.
  7. If rooms are supported, run every verification step in Authenticated rooms, including an oversize room frame.