Files
skills/docs/networking/drive/drive-webrtc/src/plugin/webrtc-plugin/webrtc-plugin.md
2026-05-29 08:20:56 +01:00

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 (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

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 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<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/drive exposes DrivePlugin<THandle> 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).