first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
# `WebRTCDrivePlugin`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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).