# DataChannelManager _(no submodules)_ ## Purpose `DataChannelManager` opens, multiplexes, and gates writes on `RTCDataChannel`s for the drive-webrtc mesh. It composes a `WebRTCClient` — pulling out the underlying `RTCPeerConnection` per peer — and exposes a uniform send/close/event surface so callers (chunk-source layer, plugin glue, debug tools) never touch raw `RTCDataChannel` event listeners. Backpressure-aware `send` waits for `bufferedamountlow` when the channel is buffering above the high-water mark, preventing the WebRTC stack from being saturated during burst transfers. ## Features - Per-peer + per-label channel multiplexing. - Default channel label `drive-chunks` and ordered delivery (configurable). - High-water-mark backpressure (default 1 MiB) with AbortSignal cancellation. - Idempotent `close` and `closeAll` semantics. - Typed event surface: `channel:open`, `channel:close`, `channel:message`, `channel:error`. - All errors raised at the API boundary are `DriveWebRtcError(PEER_NOT_FOUND | DATA_CHANNEL_CLOSED)`. ## Usage: Basic Example ```ts import { DataChannelManager, WebRTCClient } from "@suaveplan/drive-webrtc"; const manager = new DataChannelManager({ client }); const channel = manager.open("bob"); manager.on("channel:message", ({ peerId, data }) => { // route inbound payload }); await manager.send("bob", "drive-chunks", "hello"); ``` ## Usage: Advanced Example ```ts import { DataChannelManager } from "@suaveplan/drive-webrtc"; const manager = new DataChannelManager({ client, defaultLabel: "drive-chunks", defaultOrdered: true, highWaterMarkBytes: 256_000, }); const ctrl = new AbortController(); setTimeout(() => ctrl.abort(), 5_000); try { for (const part of parts) { await manager.send("bob", "drive-chunks", part, ctrl.signal); } } catch (err) { if (err instanceof DriveWebRtcError && err.code === "DRIVE_WEBRTC.DATA_CHANNEL_CLOSED") { // either peer dropped or signal aborted } } manager.close("bob"); ``` ## API Reference ### `new DataChannelManager(options)` - `client: WebRTCClient`. - `defaultLabel?: string` (default `"drive-chunks"`). - `defaultOrdered?: boolean` (default `true`). - `highWaterMarkBytes?: number` (default `1_048_576`). ### `open(peerId, label?, init?): RTCDataChannel` Reuse an open channel for the label, or create a new one via `pc.createDataChannel`. Throws `PEER_NOT_FOUND` if the client has no peer connection for `peerId`. ### `send(peerId, label, payload, signal?): Promise` Throws `DATA_CHANNEL_CLOSED` when the channel is missing or its `readyState` is not `"open"`. When `bufferedAmount` is above the high-water mark, waits for one `bufferedamountlow` event before calling `channel.send`. AbortSignal aborts the wait and the resulting error has `cause` set to the signal's reason. ### `close(peerId, label?, reason?)` / `closeAll(reason?)` Idempotent. `close(peerId)` with no label closes every channel for the peer. ### `get(peerId, label?): RTCDataChannel | undefined` Lookup. Defaults to the manager's `defaultLabel`. ## Implementation Notes - The manager listens to `open`, `close`, `error`, and `message` events on every channel it creates. It removes those listeners on detach to avoid leaking after `close`. - On `register`, if the channel is already `"open"` (an unusual race the test doubles can reproduce), `channel:open` is dispatched via `queueMicrotask` so callers awaiting the event still receive it. - `error` events surface as `channel:error` carrying a `DriveWebRtcError(DATA_CHANNEL_CLOSED)` with the underlying `event.error` as `cause`. - The internal storage is `Map>`; the outer map is pruned when its inner map becomes empty so a `closeAll` returns the manager to a fully-clean state.