Overview
The CloudGaming signaling protocol uses JSON messages over WebSocket to coordinate WebRTC connections. All messages are validated using Zod schemas before being forwarded to peers.
Message Validation
The server validates all incoming messages against strict schemas. Invalid messages are:
- Dropped silently
- Logged with warning level
- Trigger a
schema-error control message to the sender
- Increment the
signaling_schema_rejects_total metric
Non-JSON messages are rejected immediately and not processed.
Message Types
The protocol supports four primary message types:
- offer - WebRTC session description (SDP offer)
- answer - WebRTC session description (SDP answer)
- candidate - ICE candidate for connection establishment
- control - Server control messages
Offer Message
Sent by the host to initiate a WebRTC connection with a peer.
Schema
Validation Rules
type must be exactly "offer"
sdp must be a non-empty string
sdp should be valid SDP format (starts with v=)
Example
Usage
Client-side (JavaScript):
Server behavior:
- Validates message schema
- Publishes to Redis:
room:ROOM_ID
- Forwards to all peers in room (except sender)
- Logs:
"Received from server: { type: offer, sdp: '...', candidate: undefined }"
Answer Message
Sent by the client in response to an offer.
Schema
Validation Rules
type must be exactly "answer"
sdp must be a non-empty string
sdp should be valid SDP format (starts with v=)
Example
Usage
Client-side (JavaScript):
Server behavior:
- Validates message schema
- Publishes to Redis:
room:ROOM_ID
- Forwards to all peers in room (except sender)
- Typically forwarded to the original offer sender
Candidate Message
Sends ICE candidates for NAT traversal and connection establishment.
Schema
Validation Rules
type must be exactly "candidate"
candidate must be a non-empty string
sdpMid is optional string
sdpMLineIndex is optional non-negative integer
Example
Host candidate:
Server reflexive candidate:
Relay candidate (TURN):
End of candidates signal:
ICE Candidate Types
Usage
Client-side (JavaScript):
Server behavior:
- Validates message schema
- Publishes to Redis:
room:ROOM_ID
- Forwards to all peers in room (except sender)
- Empty candidate string is valid (end-of-candidates)
The client also supports an older format for backwards compatibility:
The ice-candidate format is not validated by the server schema. Use the candidate format for proper validation.
Control Messages
Server-to-client control and informational messages.
Schema
Validation Rules
type must be exactly "control"
action must be a non-empty string
payload is optional and can be any JSON-serializable value
Control Actions
schema-error
Sent by the server when a client message fails validation.
Example:
Trigger conditions:
- Invalid JSON
- Missing required fields
- Wrong field types
- Empty strings where non-empty required
Client handling:
peer-disconnected
Sent to all room members when a peer leaves.
Example:
peer-disconnected uses a simplified schema without the control action field for backwards compatibility.
Server behavior:
- Broadcast when client disconnects (WebSocket close)
- Broadcast when client removed from Redis room
- Published to Redis channel:
room:ROOM_ID
- Forwarded to all remaining peers
Client handling:
Example implementation:
Message Flow Examples
Complete Connection Establishment
Disconnection Flow
Error Scenarios
Invalid Message Type
Sent:
Result:
- Message validation fails
- Server logs:
"Dropping invalid signaling message"
- Server sends:
{ "type": "control", "action": "schema-error" }
- Message not forwarded
signaling_schema_rejects_total metric incremented
Missing Required Field
Sent:
Result:
- Schema validation fails on missing
sdp
- Server logs:
"Dropping invalid signaling message"
- Server sends:
{ "type": "control", "action": "schema-error" }
- Message not forwarded
Empty SDP String
Sent:
Result:
- Schema validation fails (minimum length: 1)
- Server logs:
"Dropping invalid signaling message"
- Server sends:
{ "type": "control", "action": "schema-error" }
- Message not forwarded
Sent:
Result:
- JSON parse fails
- Server logs:
"Dropping non-JSON client message"
- No control message sent (can’t send to unparseable client)
- Message not forwarded
Message Size Limits
Maximum message size: messageMaxBytes (default: 64KB)
Oversized message behavior:
Typical message sizes:
- ICE candidate: 100-300 bytes
- SDP offer: 2-10 KB
- SDP answer: 2-10 KB
- Control message: 50-100 bytes
For very large SDP descriptions (multiple codecs, many media lines), consider stripping unnecessary attributes or using SDP compression.
Rate Limiting Impact
Messages are subject to multiple rate limits:
- Per-client token bucket: 100 msg/10s (default)
- Per-IP rate limit: 500 msg/10s (default)
- Per-room rate limit: 1000 msg/10s (default)
When rate limited:
- Message dropped silently
- Server logs:
"Rate limit exceeded, dropping message"
signaling_rate_limit_drops_total metric incremented
- No error message sent to client
Best practice: Implement client-side rate limiting and exponential backoff.
Schema Validation Implementation
The server uses Zod for runtime type validation:
TypeScript Definitions
For TypeScript clients, use these type definitions:
Testing Messages
Test message validation using the WebSocket connection: