40 KiB
@suaveplan/drive-webrtc - WebRTC P2P mesh plugin for @suaveplan/drive: signaling, peer management, data channels, chunk discovery, real signature verification.
Summary
Browser-tier WebRTC P2P chunk-distribution plugin for
@suaveplan/drive. Provides a complete mesh
networking stack — signalling client, ICE-aware WebRTC client, peer
manager, data-channel manager, integrity verifier, chunk-discovery
engine, and a ChunkSource consumable by Drive — wrapped in a single
WebRTCDrivePlugin orchestrator. Every layer is constructed with real
implementations (@suaveplan/crypto-curve Ed25519, @suaveplan/crypto-hash
SHA-256, @suaveplan/events event bus, @suaveplan/error error
codes), and every async path threads AbortSignal so cooperative
cancellation works end-to-end.
This package targets the browser (globalThis.WebSocket,
globalThis.RTCPeerConnection) but accepts constructor injections so
tests and SSR contexts can wire deterministic doubles.
Documentation tree
- chunk-source — (no submodules)
- chunk-source — (no submodules)
- client — (no submodules)
- client — (no submodules)
- ice — (no submodules)
- ice — (no submodules)
- offer-answer — (no submodules)
- offer-answer — (no submodules)
- reconnect — (no submodules)
- reconnect — (no submodules)
- data-channel-manager — (no submodules)
- data-channel-manager — (no submodules)
- chunk-discovery-manager — (no submodules)
- chunk-discovery-manager — (no submodules)
- error-codes — The
error-codes.tsmodule defines the closed registry of error codes that@suaveplan/drive-webrtcmay throw, plus t…- error-codes — The
error-codes.tsmodule defines the closed registry of error codes that@suaveplan/drive-webrtcmay throw, plus t…
- error-codes — The
- typed-event-emitter — (no submodules)
- typed-event-emitter — (no submodules)
- chunk-integrity — (no submodules)
- chunk-integrity — (no submodules)
- lru-cache — (no submodules)
- lru-cache — (no submodules)
- peer-bandwidth-tracker — (no submodules)
- peer-bandwidth-tracker — (no submodules)
- peer-manager — (no submodules)
- peer-manager — (no submodules)
- full-mesh — Every peer connects to every other peer up to a configurable cap. This is the default topology and matches the assumpti…
- hub-and-spoke —
HubAndSpokeStrategypins the mesh into a star topology: a single designated hub accepts connections from every spoke,… - super-peer — The super-peer topology lets well-resourced nodes act as relays for the rest of the mesh. Super peers form a full mesh…
- topology-strategy —
TopologyStrategyis the pure-function contractPeerManagerconsults when a new peer is announced. A strategy decide… - webrtc-plugin — (no submodules)
- webrtc-plugin — (no submodules)
- rate-limiter — (no submodules)
- rate-limiter — (no submodules)
- config.schemas — The
config.schemas.tsmodule defines Zod boundary schemas for the configuration surface declared in `types/config.typ… - signaling.schemas — The
signaling.schemas.tsmodule exposes Zod schemas mirroring every variant ofSignalingMessagefrom `types/message… - security-validator — (no submodules)
- security-validator — (no submodules)
- signaling-client — (no submodules)
- signaling-client — (no submodules)
- sub-chunk — (no submodules)
- sub-chunk — (no submodules)
- hub-and-spoke —
HubAndSpokeStrategypins the mesh into a star topology: a single designated hub accepts connections from every spoke,… - super-peer — The super-peer topology lets well-resourced nodes act as relays for the rest of the mesh. Super peers form a full mesh…
- topology-strategy —
TopologyStrategyis the pure-function contractPeerManagerconsults when a new peer is announced. A strategy decide… - webrtc-plugin — (no submodules)- webrtc-plugin — (no submodules)- rate-limiter — (no submodules)- rate-limiter — (no submodules)- config.schemas — The
config.schemas.tsmodule defines Zod boundary schemas for the configuration surface declared in `types/config.typ… - signaling.schemas — The
signaling.schemas.tsmodule exposes Zod schemas mirroring every variant ofSignalingMessagefrom `types/message… - security-validator — (no submodules)- security-validator — (no submodules)- signaling-client — (no submodules)- signaling-client — (no submodules)- sub-chunk — (no submodules)- sub-chunk — (no submodules)- error-codes — The
error-codes.tsmodule defines the closed registry of error codes that@suaveplan/drive-webrtcmay throw, plus t… - chunk-source — (no submodules)- client — (no submodules)- client — (no submodules)- ice — (no submodules)- ice — (no submodules)- offer-answer — (no submodules)- offer-answer — (no submodules)- reconnect — (no submodules)- reconnect — (no submodules)- data-channel-manager — (no submodules)- data-channel-manager — (no submodules)- chunk-discovery-manager — (no submodules)- chunk-discovery-manager — (no submodules)- typed-event-emitter — (no submodules)- typed-event-emitter — (no submodules)- chunk-integrity — (no submodules)- chunk-integrity — (no submodules)- lru-cache — (no submodules)- lru-cache — (no submodules)- peer-bandwidth-tracker — (no submodules)- peer-bandwidth-tracker — (no submodules)- peer-manager — (no submodules)- peer-manager — (no submodules)- full-mesh — Every peer connects to every other peer up to a configurable cap. This is the default topology and matches the assumpti…
- hub-and-spoke —
HubAndSpokeStrategypins the mesh into a star topology: a single designated hub accepts connections from every spoke,… - super-peer — The super-peer topology lets well-resourced nodes act as relays for the rest of the mesh. Super peers form a full mesh…
- topology-strategy —
TopologyStrategyis the pure-function contractPeerManagerconsults when a new peer is announced. A strategy decide… - webrtc-plugin — (no submodules)- webrtc-plugin — (no submodules)- rate-limiter — (no submodules)- rate-limiter — (no submodules)- config.schemas — The
config.schemas.tsmodule defines Zod boundary schemas for the configuration surface declared in `types/config.typ… - signaling.schemas — The
signaling.schemas.tsmodule exposes Zod schemas mirroring every variant ofSignalingMessagefrom `types/message… - security-validator — (no submodules)- security-validator — (no submodules)- signaling-client — (no submodules)- signaling-client — (no submodules)- sub-chunk — (no submodules)- sub-chunk — (no submodules)- error-codes — The
error-codes.tsmodule defines the closed registry of error codes that@suaveplan/drive-webrtcmay throw, plus t… - chunk-source — (no submodules)- client — (no submodules)- client — (no submodules)- ice — (no submodules)- ice — (no submodules)- offer-answer — (no submodules)- offer-answer — (no submodules)- reconnect — (no submodules)- reconnect — (no submodules)- data-channel-manager — (no submodules)- data-channel-manager — (no submodules)- chunk-discovery-manager — (no submodules)- chunk-discovery-manager — (no submodules)- typed-event-emitter — (no submodules)- typed-event-emitter — (no submodules)- chunk-integrity — (no submodules)- chunk-integrity — (no submodules)- lru-cache — (no submodules)- lru-cache — (no submodules)- peer-bandwidth-tracker — (no submodules)- peer-bandwidth-tracker — (no submodules)- peer-manager — (no submodules)- peer-manager — (no submodules)- full-mesh — Every peer connects to every other peer up to a configurable cap. This is the default topology and matches the assumpti…
- hub-and-spoke —
HubAndSpokeStrategypins the mesh into a star topology: a single designated hub accepts connections from every spoke,… - super-peer — The super-peer topology lets well-resourced nodes act as relays for the rest of the mesh. Super peers form a full mesh…
- topology-strategy —
TopologyStrategyis the pure-function contractPeerManagerconsults when a new peer is announced. A strategy decide… - webrtc-plugin — (no submodules)- webrtc-plugin — (no submodules)- rate-limiter — (no submodules)- rate-limiter — (no submodules)- config.schemas — The
config.schemas.tsmodule defines Zod boundary schemas for the configuration surface declared in `types/config.typ… - signaling.schemas — The
signaling.schemas.tsmodule exposes Zod schemas mirroring every variant ofSignalingMessagefrom `types/message… - security-validator — (no submodules)- security-validator — (no submodules)- signaling-client — (no submodules)- signaling-client — (no submodules)- sub-chunk — (no submodules)- sub-chunk — (no submodules)- error-codes — The
error-codes.tsmodule defines the closed registry of error codes that@suaveplan/drive-webrtcmay throw, plus t… - chunk-source — (no submodules)- client — (no submodules) - client — (no submodules)- ice — (no submodules) - ice — (no submodules)- offer-answer — (no submodules) - offer-answer — (no submodules)- reconnect — (no submodules) - reconnect — (no submodules)- data-channel-manager — (no submodules) - data-channel-manager — (no submodules)- chunk-discovery-manager — (no submodules) - chunk-discovery-manager — (no submodules)- typed-event-emitter — (no submodules) - typed-event-emitter — (no submodules)- chunk-integrity — (no submodules) - chunk-integrity — (no submodules)- lru-cache — (no submodules) - lru-cache — (no submodules)- peer-bandwidth-tracker — (no submodules) - peer-bandwidth-tracker — (no submodules)- peer-manager — (no submodules) - peer-manager — (no submodules)- full-mesh — Every peer connects to every other peer up to a configurable cap. This is the default topology and matches the assumpti…
- hub-and-spoke —
HubAndSpokeStrategypins the mesh into a star topology: a single designated hub accepts connections from every spoke,… - super-peer — The super-peer topology lets well-resourced nodes act as relays for the rest of the mesh. Super peers form a full mesh…
- topology-strategy —
TopologyStrategyis the pure-function contractPeerManagerconsults when a new peer is announced. A strategy decide… - webrtc-plugin — (no submodules) - webrtc-plugin — (no submodules)- rate-limiter — (no submodules) - rate-limiter — (no submodules)- config.schemas — The
config.schemas.tsmodule defines Zod boundary schemas for the configuration surface declared in `types/config.typ… - signaling.schemas — The
signaling.schemas.tsmodule exposes Zod schemas mirroring every variant ofSignalingMessagefrom `types/message… - security-validator — (no submodules) - security-validator — (no submodules)- signaling-client — (no submodules) - signaling-client — (no submodules)- sub-chunk — (no submodules) - sub-chunk — (no submodules)- error-codes — The
error-codes.tsmodule defines the closed registry of error codes that@suaveplan/drive-webrtcmay throw, plus t…
Quick Start
bun add @suaveplan/drive-webrtc
import { Drive } from "@suaveplan/drive";
import { WebRTCDrivePlugin } from "@suaveplan/drive-webrtc";
const plugin = new WebRTCDrivePlugin({
peerId: crypto.randomUUID(),
config: {
signaling: { url: "wss://signal.example.com/p2p" },
p2p: {
iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }],
},
},
signalingUrl: "wss://signal.example.com/p2p",
});
await plugin.start();
const drive = new Drive(/* storage */);
// Drive integration: hand the chunk source to your read pipeline.
const bytes = await plugin.chunkSource.fetchChunk("chunk:42");
// Subscribe to any of the plugin-level events.
plugin.on("webrtc:connected", ({ peerId }) => {
console.info(`p2p peer ${peerId} is up`);
});
// On shutdown:
await plugin.stop();
Subpath Map
Every internal subsystem is published under a dedicated subpath so
downstream apps tree-shake to the minimum surface they need. Eleven
tree-shake snapshots in tests/bundle-size/ enforce the leaf
boundaries.
| Subpath | Primary export | Purpose |
|---|---|---|
. (root) |
Everything | Convenience barrel — re-exports every public surface below. |
./plugin/webrtc-plugin |
WebRTCDrivePlugin |
Top-level orchestrator. Start here. |
./client/webrtc-client |
WebRTCClient, resolveConfig |
ICE-aware peer-connection lifecycle. |
./signaling/signaling-client |
SignalingClient |
WebSocket signalling with bounded-backoff reconnect. |
./peer-manager/peer-manager |
PeerManager |
Peer admission / eviction + bandwidth-scored ranking. |
./data-channel/data-channel-manager |
DataChannelManager |
Multiplexed RTCDataChannel lifecycle + backpressure. |
./integrity/chunk-integrity |
ChunkIntegrityManager |
Real SHA-256 + Ed25519 verify per inbound chunk. |
./chunk-source/chunk-source |
WebRTCChunkSource |
Drive-facing ChunkSource backed by the discovery manager. |
./rate-limiter/rate-limiter |
RateLimiter |
Token-bucket rate limiter. |
./lru-cache/lru-cache |
LRUCache |
Bounded LRU cache for chunk hot-path memoisation. |
./security/security-validator |
validateSignalingUrl, generateSecurePeerId, ... |
UUID v4 + signalling-URL guards. |
./sub-chunk/sub-chunk-splitter |
splitIntoSubChunks, SubChunkReassembler |
Sub-chunk fragmentation + reassembly for data-channel transfers. |
./errors |
DRIVE_WEBRTC_CODES, DriveWebRtcError |
Stable typed error registry. |
./schemas |
Zod schemas | Wire-format validation for every signalling + config surface. |
ICE / TURN Operations
WebRTC peers exchange ICE candidates over signalling; in production
networks behind symmetric NATs or restrictive firewalls, peers cannot
reach each other directly and require a TURN relay. The p2p.iceServers
list is exhaustively typed as RTCIceServer[]:
{
p2p: {
iceServers: [
// STUN: enough on permissive networks.
{ urls: ["stun:stun.l.google.com:19302"] },
// TURN: relays the media path through your infra; required
// for restrictive corp / cell networks. Costs egress.
{
urls: [
"turn:turn.example.com:3478?transport=udp",
"turn:turn.example.com:3478?transport=tcp",
"turns:turn.example.com:5349?transport=tcp",
],
username: "user",
credential: "secret",
},
],
},
}
TURN URL format
turn://username:credential@host:port?transport={udp,tcp}
turns://username:credential@host:port?transport=tcp
turn: is unencrypted; turns: runs TURN over TLS (required for some
mobile carrier networks). Always provide BOTH UDP and TCP transports —
some networks block UDP/3478 entirely.
ICE-restart policy
WebRTCClient automatically restarts ICE up to maxIceRestartAttempts
times (default 3) when the connection enters the failed state. After
the cap is exhausted, ice:state reports failed once more without a
further restart and the plugin evicts the peer. Tune the cap with the
maxIceRestartAttempts option on WebRTCClient (the plugin forwards
this through config).
Exhaustion symptoms
- Many
webrtc:failedevents: peers cannot complete ICE — check TURN reachability, credentials, port allowlists. - Bandwidth blackholes: high
bytesIn, zerobytesOut— direct path blocked, TURN relay forced; verify TURN provider quota. signaling:reconnectstorms: signalling URL or TLS misconfiguration — checkSignalingClient.state+reconnect:exhaustedevents.
Topology Strategies
PeerManager consumes a TopologyStrategy. Three are bundled:
FullMeshStrategy(default): every peer connects to every other peer. Best for small meshes (<30 peers) where every pair benefits from a direct edge. Memory + CPU scales O(n^2) with mesh size.SuperPeerStrategy: nominate one or more high-bandwidth / high-uptime peers as super peers; other peers only connect to super peers + a small set of equal peers. Best for asymmetric capacity (server-grade super peer + browser leaves). Reduces edge count to O(n).HubAndSpokeStrategy: every peer connects to one of a small configured hub set. Best for centralised topologies (e.g. server- assisted mesh). Lowest edge count, simplest debug surface, no peer- to-peer privacy.
Pick FullMeshStrategy by default. Move to SuperPeerStrategy when
peer counts exceed ~20 and HubAndSpokeStrategy when the application
is fundamentally hub-mediated.
Telemetry
Every long-lived component emits OpenTelemetry instrumentation. Names are stable across patch releases:
| Metric | Type | Attributes | Description |
|---|---|---|---|
drive.webrtc.peers_connected_total |
counter | - | Peers that have reached ICE connected. |
drive.webrtc.chunks_served_total |
counter | outcome |
Local chunk-serve outcomes (ok/not-found/...). |
drive.webrtc.peer_rtt_ms |
histogram | peer_id |
Round-trip latency per peer (ms). |
drive.webrtc.signaling_reconnect_ms |
histogram | attempt,outcome |
Reconnect wall-time. |
drive.webrtc.signaling_messages_total |
counter | direction,outcome |
Signalling messages by direction (in/out) + outcome. |
Every collaborator additionally emits its own span (signaling.connect,
plugin.start, webrtc.ice.restart, ...). Wire your OTel exporter at
process boot — the package does nothing else to initialise the API.
Architecture (text diagram)
+---------------------------+
| WebRTCDrivePlugin |
| (start / stop / stats) |
+-----+----------+----------+
| |
+-------------+ +--------------+
v v
+-------------------+ +----------------------+
| SignalingClient | wss frames | WebRTCClient |
| (WebSocket) | <--------------> | (RTCPeerConnection) |
+---------+---------+ +----------+-----------+
| |
| peer-joined / peer-left | datachannel
v v
+-------------------+ +----------------------+
| PeerManager | selects peers | DataChannelManager |
| + topology | <--------------> | + backpressure |
+---------+---------+ +----------+-----------+
| |
| peer rank, bandwidth | sub-chunk
v v
+-------------------+ +----------------------+
| PeerBandwidth | | SubChunkReassembler |
| Tracker | | |
+-------------------+ +----------+-----------+
|
v
+----------------------+
| ChunkDiscoveryMgr |
| (ranks, dedups, ver) |
+----------+-----------+
|
v
+----------------------+
| ChunkIntegrityMgr |
| (SHA-256, Ed25519) |
+----------+-----------+
|
v
+----------------------+
| WebRTCChunkSource |
| (Drive surface) |
+----------------------+
Leaf utilities (LRUCache, RateLimiter, security-validator,
sub-chunk-splitter) are referenced from the components above but have
no upward dependency — they remain individually importable.
Configuration Schemas
All wire-format and configuration shapes are validated with Zod under
./schemas. The discriminator-bearing
SignalingMessageSchema is the single source of truth for every
signalling frame; WebRTCPluginConfigSchema validates plugin
construction options. Direct schema access is available via:
import {
SignalingMessageSchema,
WebRTCPluginConfigSchema,
} from "@suaveplan/drive-webrtc/schemas";
Error Codes
Every boundary throws DriveWebRtcError carrying one of the codes from
DRIVE_WEBRTC_CODES:
SIGNALING_UNREACHABLEPEER_HANDSHAKE_FAILEDCHUNK_INTEGRITY_FAILEDICE_FAILEDPEER_BANDWIDTH_EXCEEDEDRATE_LIMIT_EXCEEDEDSIGNALING_MESSAGE_INVALIDCONFIG_INVALIDPEER_NOT_FOUNDDATA_CHANNEL_CLOSEDSUB_CHUNK_TIMEOUTSUB_CHUNK_MISSINGCHUNK_NOT_AVAILABLE
Import them directly from the leaf module:
import {
DRIVE_WEBRTC_CODES,
DriveWebRtcError,
} from "@suaveplan/drive-webrtc/errors";
Drive plugin contract deviation
The Drive package exposes the DrivePlugin<THandle> interface with an
install(drive) shape. The new WebRTCDrivePlugin orchestrator surface
specified by the migration uses start() / stop() / stats()
instead — these two contracts are not directly compatible. The
orchestrator class therefore exposes its own lifecycle and a
name: "drive-webrtc" literal; consumers wire it to Drive with a thin
adapter:
const adapter = {
name: "drive-webrtc",
async install() {
await plugin.start();
return { chunkSource: plugin.chunkSource };
},
async dispose() {
await plugin.stop();
},
};
await drive.usePlugin(adapter);
This deviation is intentional and documented in webrtc-plugin.md.
License
MIT.