4.7 KiB
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
SignalingMessagevariant SignalingMessageSchemaz.unionover all 17 — handlesrequest-chunkshape overlap- Strict u64 decimal-string regex for
fileId/chunkId - Strict
RTCSessionDescriptionInitandRTCIceCandidateInitsubstructures - Non-empty string enforcement on
requestId, peer IDs, chunk keys, base64data - All schemas are pure validators — no transforms, no side effects
Usage
Basic Example
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
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,IceCandidateMessageSchemaRegisterMessageSchema,RegisteredMessageSchemaPeerJoinedMessageSchema,PeerLeftMessageSchemaAnnounceChunksMessageSchema,ChunksUpdatedMessageSchemaChunkRequestMessageSchema,ChunkDataMessageSchemaChunkDataPartMessageSchemaDataChannelChunkRequestSchemaChunkQueryMessageSchema,ChunkQueryResponseSchemaChunkBroadcastRequestSchema,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-chunkis reused by bothChunkRequestMessage(signaling-relayed, nestedpayloadobject) andDataChannelChunkRequest(flat shape, nopayload).z.discriminatedUnionrequires unique discriminator values — we usez.unioninstead 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.
fileIdandchunkIdare validated as decimal-string u64s (/^\d+$/). The schema does NOT range-check against 2^64; the consumer shouldBigInt(...)and then reject any value above2n ** 64n - 1nif that matters.- The
RTCSessionDescriptionInitschema accepts the four standardtypevalues (offer/answer/pranswer/rollback). Thesdpfield is optional becauserollbacklegitimately omits it. - The
RTCIceCandidateInitsubstructure permitsnullonsdpMLineIndex/sdpMid/usernameFragmentbecause the browser DOM definitions allow it. datafields (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.