Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# 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.