# @suaveplan/drive-transport-webrtc — WebRTC DriveTransport WebRTC datachannel implementation of the `DriveTransport` interface for browser-to-browser chunk transfer. Composes `WebRTCClient` + `DataChannelManager` from `@suaveplan/drive-webrtc`; consumed by `@suaveplan/drive-mesh` alongside `drive-transport-https` as one of the pluggable wire transports. ## Requirements ### Requirement: DriveTransport contract The package SHALL expose `WebRTCDriveTransport` implementing the structural `DriveTransport` interface: - `name: "drive-transport-webrtc"` - `requestChunk(peerId, fileId, chunkId, signal?): AsyncIterable` - `advertise(peerId, fileId, bitset): Promise` - `onAdvertisement(handler): () => void` - `peers(): Iterable` - `dispose(): Promise` The local `DriveTransport` interface MUST remain structurally compatible with the canonical one defined in `@suaveplan/drive-mesh` so consumers can swap to the mesh-canonical type without code changes when mesh lands. ### Requirement: Wire framing Each datachannel message SHALL be a length-prefixed binary frame: ```text [4 B uint32 BE frameLen] [1 B uint8 frameType] // 0x01 chunk-request, 0x02 chunk-response, 0x03 advertisement [frameLen-1 B payload] ``` - `MAX_FRAME_BYTES = 16 MiB`; over-cap frames MUST be rejected with `DriveTransportWebRtcError(FRAME_TOO_LARGE)`. - Malformed frames (length mismatch, unknown type) MUST raise `FRAME_INVALID`. - Chunk request payload: `fileId(32) ‖ chunkId(4 BE uint32)`. - Chunk response payload: raw chunk bytes (no extra length prefix — frame already carries `frameLen`). - Advertisement payload: `fileId(32) ‖ gzipped-bitset`. Compression uses the platform `CompressionStream`/`DecompressionStream` API; the stream ctors are injectable via `StreamCtorResolver` so tests cover missing- stream branches without `vi.stubGlobal`. ### Requirement: requestChunk semantics `requestChunk(peerId, fileId, chunkId, signal?)`: 1. Reuse existing channel via `dataChannels.get(peerId, defaultLabel)` or `open(peerId, defaultLabel)`. Unknown peer → `PEER_NOT_CONNECTED`. 2. Encode + send chunk-request frame. 3. Wait for the next chunk-response frame from the same `(peerId, label)` within `requestTimeoutMs`. AbortSignal cancels the wait + throws `ABORTED`. Timeout throws `REQUEST_TIMEOUT`. 4. Yield the response payload as a single value of the `AsyncIterable`. 5. Chunk-responses FIFO-match per peer — the wire format has no request-id field. Documented in implementation notes as a known design constraint. ### Requirement: advertise + onAdvertisement `advertise(peerId, fileId, bitset)`: - Gzip the bitset via `CompressionStream`. Compression failure throws `BITSET_COMPRESSION_FAILED`. - Encode advertisement frame, send via `DataChannelManager.send`. - Underlying send failure (channel closed) maps to `DATA_CHANNEL_CLOSED`. `onAdvertisement(handler)` subscribes to `dataChannels.on("channel:message")`, decodes incoming frames, invokes `handler` only for `frameType === 0x03`, returns an unsubscribe function. ### Requirement: AbortSignal threading Every async path (`requestChunk`, `advertise`, listener wait) SHALL accept and honour an optional `AbortSignal`. Pre-aborted signals throw `DriveTransportWebRtcError(ABORTED)` before any side effect. ### Requirement: dispose idempotency `dispose()` SHALL unsubscribe from `DataChannelManager` listeners and release internal state. It MUST be safe to call multiple times. It MUST NOT close the underlying `WebRTCClient` — client lifetime is managed by the parent (typically `WebRTCDrivePlugin` or `DriveMesh`). ### Requirement: OTel observability The package SHALL register the following instruments under tracer `@suaveplan/drive-transport-webrtc`: - Counter `drive.transport.webrtc.requests_total{outcome}` - Counter `drive.transport.webrtc.advertisements_total{direction}` - Histogram `drive.transport.webrtc.request_duration_ms` - Histogram `drive.transport.webrtc.frame_size_bytes` ### Requirement: Tests use injected real fakes Tests MUST NOT use `vi.mock` or `vi.stubGlobal`. The `FakeRTCPeerConnection` / `FakeRTCDataChannel` real-implementation pattern from `@suaveplan/drive-webrtc` MUST be reused (structural `EventTarget` subclasses with `simulate*` driver methods). All test imports come from `@suaveplan/testing/runner`. ### Requirement: Coverage + gate `bunx turbo lint typecheck test:coverage --filter=@suaveplan/drive-transport-webrtc` MUST be green and istanbul coverage MUST be 100% on every touched module before any release.