# Signaling Schemas ## Purpose The `signaling.schemas.ts` module exposes Zod schemas mirroring every variant of `SignalingMessage` from `types/messages.types.ts`. Inbound traffic — both from the signaling WebSocket and from peer data channels — MUST be parsed against `SignalingMessageSchema` before any handler sees the value. The package treats anything that fails to parse as `DRIVE_WEBRTC.SIGNALING_MESSAGE_INVALID`, isolating untrusted input at the boundary so internal code can trust message shapes. Schemas here are deliberately strict: required fields cannot be omitted, decimal-string u64 IDs must match `/^\d+$/`, and `chunks` arrays cannot contain empty strings. Permissive fields (`from`, `to`, `chunkIds`) use `.optional()` because the wire format allows them to be omitted. ## Features - 17 per-variant schemas, one per `SignalingMessage` variant - `SignalingMessageSchema` `z.union` over all 17 — handles `request-chunk` shape overlap - Strict u64 decimal-string regex for `fileId`/`chunkId` - Strict `RTCSessionDescriptionInit` and `RTCIceCandidateInit` substructures - Non-empty string enforcement on `requestId`, peer IDs, chunk keys, base64 `data` - All schemas are pure validators — no transforms, no side effects ## Usage ### Basic Example ```typescript import { SignalingMessageSchema } from "@suaveplan/drive-webrtc/schemas"; const result = SignalingMessageSchema.safeParse(JSON.parse(rawWire)); if (!result.success) { // throw DriveWebRtcError(SIGNALING_MESSAGE_INVALID, ...) } const msg = result.data; ``` ### Advanced Example ```typescript import { ChunkDataPartMessageSchema, DRIVE_WEBRTC_CODES, DriveWebRtcError, SignalingMessageSchema, } from "@suaveplan/drive-webrtc"; function parseFromDataChannel(raw: string): unknown { let value: unknown; try { value = JSON.parse(raw); } catch (cause) { throw new DriveWebRtcError( DRIVE_WEBRTC_CODES.SIGNALING_MESSAGE_INVALID, "not valid JSON", { cause: cause instanceof Error ? cause : undefined }, ); } // Sub-chunk fragments are hot-path — try the fast schema first const fast = ChunkDataPartMessageSchema.safeParse(value); if (fast.success) return fast.data; const slow = SignalingMessageSchema.safeParse(value); if (!slow.success) { throw new DriveWebRtcError( DRIVE_WEBRTC_CODES.SIGNALING_MESSAGE_INVALID, slow.error.message, ); } return slow.data; } ``` ## API Reference Per-variant schemas (all exported): - `OfferMessageSchema`, `AnswerMessageSchema`, `IceCandidateMessageSchema` - `RegisterMessageSchema`, `RegisteredMessageSchema` - `PeerJoinedMessageSchema`, `PeerLeftMessageSchema` - `AnnounceChunksMessageSchema`, `ChunksUpdatedMessageSchema` - `ChunkRequestMessageSchema`, `ChunkDataMessageSchema` - `ChunkDataPartMessageSchema` - `DataChannelChunkRequestSchema` - `ChunkQueryMessageSchema`, `ChunkQueryResponseSchema` - `ChunkBroadcastRequestSchema`, `ChunkBroadcastResponseSchema` ### `SignalingMessageSchema` `z.union` over all 17 per-variant schemas. Use `safeParse` to validate untrusted input. Returns the typed `SignalingMessage` on success. ## Implementation Notes - `request-chunk` is reused by both `ChunkRequestMessage` (signaling-relayed, nested `payload` object) and `DataChannelChunkRequest` (flat shape, no `payload`). `z.discriminatedUnion` requires unique discriminator values — we use `z.union` instead so Zod tries each schema in order until one matches. - The order of variants in the union matters for performance, not correctness: list hot-path variants first if profiling shows it. We currently match the typing-file order so audits are straightforward. - `fileId` and `chunkId` are validated as decimal-string u64s (`/^\d+$/`). The schema does NOT range-check against 2^64; the consumer should `BigInt(...)` and then reject any value above `2n ** 64n - 1n` if that matters. - The `RTCSessionDescriptionInit` schema accepts the four standard `type` values (`offer`/`answer`/`pranswer`/`rollback`). The `sdp` field is optional because `rollback` legitimately omits it. - The `RTCIceCandidateInit` substructure permits `null` on `sdpMLineIndex`/`sdpMid`/`usernameFragment` because the browser DOM definitions allow it. - `data` fields (base64 payloads) are validated as non-empty strings only — full base64 character-set enforcement is deliberately omitted because some browsers tolerate `/` vs `_` variants. Ciphertext integrity is verified separately after decode. - Adding a new message variant requires touching three files in one commit: `types/messages.types.ts` (the type), this file (the schema), and the union. The repo grep gate flags any drift between the three.