# `WebRTCDrivePlugin` _(no submodules)_ ## Purpose `WebRTCDrivePlugin` is the top-level orchestrator that wires every `@suaveplan/drive-webrtc` building block — signaling client, ICE-aware WebRTC client, peer manager, bandwidth tracker, data-channel manager, chunk-integrity manager, chunk-discovery manager, and chunk source — behind one cohesive `start()` / `stop()` / `stats()` surface. It is the package's primary public entry point and the only class consumers must instantiate to participate in a P2P mesh. The plugin reacts to signalling traffic, drives offer/answer exchange through the `WebRTCClient`, opens data channels when ICE reaches `connected`, evicts peers when ICE reports `failed`, and forwards locally available chunks to peers that ask for them via an injectable `localChunkProvider` callback. ## Features - Idempotent `start()` / `stop()` lifecycle. - Real Ed25519 signature verification through `ChunkIntegrityManager`. - Automatic peer admission + eviction on ICE state transitions. - Pluggable topology strategy (`FullMesh` default, `SuperPeerStrategy`, `HubAndSpokeStrategy` available). - OTel surface — `drive.webrtc.peers_connected_total`, `drive.webrtc.chunks_served_total{outcome}`, `drive.webrtc.peer_rtt_ms`. - AbortSignal threaded into `start()` so cooperative connect cancellation works through every layer. - Optional `localChunkProvider` callback for serving inbound `request-chunk` data-channel frames. - Defaults are browser-tier (`globalThis.WebSocket`, `globalThis.RTCPeerConnection`); tests inject structural doubles via `webSocketCtor` / `rtcPeerConnectionCtor`. ## Usage: Basic Example ```ts import { WebRTCDrivePlugin } from "@suaveplan/drive-webrtc"; const plugin = new WebRTCDrivePlugin({ peerId: crypto.randomUUID(), config: { signaling: { url: "wss://signal.example.com/p2p" }, }, signalingUrl: "wss://signal.example.com/p2p", }); await plugin.start(); // Drive integration: hand `plugin.chunkSource` to the Drive instance so // it can fetch chunks over the P2P mesh. const bytes = await plugin.chunkSource.fetchChunk("chunk:42"); await plugin.stop(); ``` ## Usage: Advanced Example ```ts import { HubAndSpokeStrategy, WebRTCDrivePlugin, } from "@suaveplan/drive-webrtc"; const plugin = new WebRTCDrivePlugin({ peerId: "node-a", config: { signaling: { url: "wss://signal.example.com/p2p", reconnect: true, reconnectDelay: 2_000, }, p2p: { priority: 50, iceServers: [ { urls: ["stun:stun.example.com:3478"] }, { urls: ["turn:turn.example.com:3478?transport=udp"], username: "user", credential: "secret", }, ], maxConcurrentDownloads: 8, }, }, signalingUrl: "wss://signal.example.com/p2p?peerId=node-a", topology: new HubAndSpokeStrategy({ hubPeerIds: ["hub-a"] }), maxPeers: 32, defaultPublicKey: publisherPubKey, localChunkProvider: async (chunkId) => { return cache.get(chunkId) ?? null; }, }); plugin.on("webrtc:connected", ({ peerId }) => { log.info({ peerId }, "p2p peer is up"); }); await plugin.start(abortController.signal); const bytes = await plugin.chunkSource.fetchChunk("chunk:42", { signal: abortController.signal, }); const stats = plugin.stats(); metrics.gauge("p2p.active_peers", stats.activePeers); ``` ## API Reference ### `new WebRTCDrivePlugin(options: WebRTCDrivePluginOptions)` Constructs the orchestrator and every collaborator. Does not initiate any network traffic until `start()` is called. ### `WebRTCDrivePluginOptions` - `peerId: string` — local peer id (UUID v4 recommended). - `config: WebRTCPluginConfig` — validated by `resolveConfig`. - `signalingUrl: string` — WebSocket URL for the signalling server. - `webSocketCtor?: typeof WebSocket` — optional WebSocket constructor override. Defaults to `globalThis.WebSocket`. - `rtcPeerConnectionCtor?: RTCPeerConnectionCtor` — optional override. - `topology?: TopologyStrategy` — defaults to `FullMeshStrategy`. - `maxPeers?: number` — forwarded to `PeerManager`. - `defaultPublicKey?: Uint8Array` — Ed25519 public key for chunk signature verification. - `clock?: { now(): number }` — test-injectable wall clock. - `localChunkProvider?: LocalChunkProvider` — callback that returns the chunk bytes a local node owns, or `null`. - `defaultLabel?: string` — data channel label. Defaults to `"drive-chunks"`. ### `plugin.start(signal?: AbortSignal): Promise` Connect to signalling, send `register`, install all event wiring. Idempotent: a second call is a no-op while the plugin is running. Throws `DriveWebRtcError(SIGNALING_UNREACHABLE)` if invoked after `stop()`. ### `plugin.stop(reason?: string): Promise` Tear everything down: unsubscribe handlers, close the discovery manager, close all data channels, close every peer connection, close the signalling socket. Idempotent and safe to call before `start()`. ### `plugin.stats(): WebRTCStats` Snapshot of plugin-level statistics. Fills `activePeers` (peers in `connected` state) and `totalPeers`; remaining counters stay at the `createDefaultStats()` baseline pending the broader stats integration. ### Public collaborators `client`, `signaling`, `peers`, `bandwidth`, `dataChannels`, `integrity`, `discovery`, `chunkSource`. Each is the real instance and each surfaces its own event emitter — consumers can subscribe directly when they need finer-grained events than `WebRTCEventMap`. ## Implementation Notes - The plugin extends `TypedEventEmitter` so it can surface a stable public event surface independent of the collaborator-specific event maps below it. - Drive contract deviation: the upstream `@suaveplan/drive` exposes `DrivePlugin` with `install(drive)` / `dispose?()`. The spec for this orchestrator requires a richer `start()` / `stop()` / `stats()` API; the two surfaces are not directly compatible, so this class implements the spec surface and `name: "drive-webrtc"` rather than the structural `DrivePlugin` shape. Drive integration can trivially wrap an instance in `{ name, install(){...}, dispose() }` when needed. - ICE state mapping collapses `connected` and `completed` onto the 4-value `PeerConnectionState`; all other states route to `connecting`, `disconnected`, or `failed`. - Local-chunk serving uses a minimal JSON envelope (`{type:"chunk-data", payload:{requestId, chunkId, data}}`) compatible with the discovery manager's wire format. Sub-chunked uploads remain the responsibility of the discovery / sub-chunk subsystem. - Every cross-component event subscription is recorded in a single `unsubscribers` list so `stop()` can flush them in O(n).