08 — Form Specifications (WS / Realtime Module)
- 1. Handshake inputs (not a form, but a contract)
- 2. Client→server messages
- 3. What the client must validate (proposed)
- 4. Server→client "responses" (informational, not form errors)
- 5. Future wire inputs (planned)
The realtime module has no forms — but it has wire inputs. This file specifies the only data the client ever sends, the handshake parameters, and their validation, because they are the module's equivalent of a form contract. General form/validation conventions: 00-shared/07 (API conventions) and 00-shared/03 (form primitives).
1. Handshake inputs (not a form, but a contract)
| Input | Where | Rules | Source |
|---|---|---|---|
| Access token | handshake.auth.token or handshake.query.token | required; JWT verified against JWT_ACCESS_SECRET; missing → UnauthorizedException('Missing token'); any verify failure → socket closed | ws.gateway.ts:37-44,54-56 |
| Namespace | URL path /ws | fixed | ws.gateway.ts:20-23 |
Client rules:
- Prefer
auth.token(not in URL/logs);query.tokenexists for non-engine.io clients. - Token must be the same family as REST (
JWT_ACCESS_SECRET), so refresh flow is shared. - No other handshake fields (no tenantId — server takes it from the token,
ws.gateway.ts:47).
2. Client→server messages
| Event | Payload | Validation (source) | Effect |
|---|---|---|---|
subscribe | room: string | typeof room !== 'string' → silently ignored (ws.gateway.ts:65) | join room → future broadcastToRoom delivery (ws.gateway.ts:80-82) |
unsubscribe | room: string | typeof room !== 'string' → silently ignored (ws.gateway.ts:71) | leave room |
Semantics:
- Idempotent: subscribing twice is safe (socket.io joins are idempotent).
- No acknowledgment message is sent (server does not reply — no ack/error channel in
source; verified: gateway handlers return
void). - No validation of room name content: any string accepted → gap (allow-list + RBAC recommended, 12 §5).
3. What the client must validate (proposed)
roomlength ≤ 128; only[a-z0-9:_-]charset enforced client-side to avoid injecting reserved namespaces; server-side enforcement is the real fix (gap).- Never subscribe to
tenant:{...}explicitly (automatic — duplicate is harmless).
4. Server→client "responses" (informational, not form errors)
| Situation | Server behavior | Client handling |
|---|---|---|
| Bad/missing token | close connection (no error event) | state error-auth → refresh token → reconnect (06 §1.2) |
| Successful connect | joins tenant:{tenantId} (ws.gateway.ts:50) | state connected |
| Event delivery | envelope {eventType, occurredAt, payload} (ws-bridge.service.ts:17-21) | validate envelope, route to blocs (13) |
| Unknown client event name | ignored (no handler registered) | n/a (client only sends subscribe/unsubscribe) |
5. Future wire inputs (planned)
- Communication module will define its own client→server messages (send/typing/read
receipts)
(planned)—docs/IMPLEMENTATION_PLAN.md:119. Until then the client MUST NOT send free-form messages. - Event-subscription filters (per-eventType)
(planned); today a client receives the whole tenant stream and filters locally (06 §6).