6.8 KiB
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 (
FullMeshdefault,SuperPeerStrategy,HubAndSpokeStrategyavailable). - 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
localChunkProvidercallback for serving inboundrequest-chunkdata-channel frames. - Defaults are browser-tier (
globalThis.WebSocket,globalThis.RTCPeerConnection); tests inject structural doubles viawebSocketCtor/rtcPeerConnectionCtor.
Usage: Basic Example
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
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 byresolveConfig.signalingUrl: string— WebSocket URL for the signalling server.webSocketCtor?: typeof WebSocket— optional WebSocket constructor override. Defaults toglobalThis.WebSocket.rtcPeerConnectionCtor?: RTCPeerConnectionCtor— optional override.topology?: TopologyStrategy— defaults toFullMeshStrategy.maxPeers?: number— forwarded toPeerManager.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, ornull.defaultLabel?: string— data channel label. Defaults to"drive-chunks".
plugin.start(signal?: AbortSignal): Promise<void>
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<void>
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<WebRTCEventMap>so it can surface a stable public event surface independent of the collaborator-specific event maps below it. - Drive contract deviation: the upstream
@suaveplan/driveexposesDrivePlugin<THandle>withinstall(drive)/dispose?(). The spec for this orchestrator requires a richerstart()/stop()/stats()API; the two surfaces are not directly compatible, so this class implements the spec surface andname: "drive-webrtc"rather than the structuralDrivePluginshape. Drive integration can trivially wrap an instance in{ name, install(){...}, dispose() }when needed. - ICE state mapping collapses
connectedandcompletedonto the 4-valuePeerConnectionState; all other states route toconnecting,disconnected, orfailed. - 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
unsubscriberslist sostop()can flush them in O(n).