first commit
This commit is contained in:
91
docs/networking/network/network-transport-webrtc/README.md
Normal file
91
docs/networking/network/network-transport-webrtc/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# @suaveplan/network-transport-webrtc - WebRTC transport plugin for mesh networking: RTCPeerConnection data-channel transport with injectable constructors
|
||||
|
||||
## Summary
|
||||
|
||||
WebRTC transport plugin for mesh networking.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [data-channel](./src/data-channel/data-channel-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [data-channel-manager](./src/data-channel/data-channel-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [ice](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [ice-manager](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [webrtc-mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [ping](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [ping-manager](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [signaling-client](./src/signaling/signaling-client.md) — Defines the structural contract for any signaling channel used by the WebRTC plugin to exchange offers, answers, and IC…
|
||||
- [websocket-signaling-client](./src/signaling/websocket-signaling-client.md) — Concrete `SignalingClient` implementation backed by a WebSocket. Handles the boring-but-load-bearing pieces of any sign…
|
||||
- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [error-codes](./src/error-codes.md) — Defines the stable error-code registry for the `@suaveplan/network-transport-webrtc` package. All runtime failures with…
|
||||
<!-- end:auto-docs --> - [data-channel-manager](./src/data-channel/data-channel-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice-manager](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [webrtc-mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping-manager](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [signaling-client](./src/signaling/signaling-client.md) — Defines the structural contract for any signaling channel used by the WebRTC plugin to exchange offers, answers, and IC…
|
||||
- [websocket-signaling-client](./src/signaling/websocket-signaling-client.md) — Concrete `SignalingClient` implementation backed by a WebSocket. Handles the boring-but-load-bearing pieces of any sign…
|
||||
- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable error-code registry for the `@suaveplan/network-transport-webrtc` package. All runtime failures with…
|
||||
- [data-channel-manager](./src/data-channel/data-channel-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice-manager](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [webrtc-mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping-manager](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [signaling-client](./src/signaling/signaling-client.md) — Defines the structural contract for any signaling channel used by the WebRTC plugin to exchange offers, answers, and IC…
|
||||
- [websocket-signaling-client](./src/signaling/websocket-signaling-client.md) — Concrete `SignalingClient` implementation backed by a WebSocket. Handles the boring-but-load-bearing pieces of any sign…
|
||||
- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable error-code registry for the `@suaveplan/network-transport-webrtc` package. All runtime failures with…
|
||||
- [data-channel-manager](./src/data-channel/data-channel-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice-manager](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [webrtc-mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping-manager](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [signaling-client](./src/signaling/signaling-client.md) — Defines the structural contract for any signaling channel used by the WebRTC plugin to exchange offers, answers, and IC…
|
||||
- [websocket-signaling-client](./src/signaling/websocket-signaling-client.md) — Concrete `SignalingClient` implementation backed by a WebSocket. Handles the boring-but-load-bearing pieces of any sign…
|
||||
- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable error-code registry for the `@suaveplan/network-transport-webrtc` package. All runtime failures with…
|
||||
- [data-channel-manager](./src/data-channel/data-channel-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [ice](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ice-manager](./src/ice/ice-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_ - [webrtc-mesh](./src/mesh/webrtc-mesh.md) — <!-- begin:auto-docs --> _(no submodules)_- [ping](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ping-manager](./src/ping/ping-manager.md) — <!-- begin:auto-docs --> _(no submodules)_- [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_ - [plugin](./src/plugin/plugin.md) — <!-- begin:auto-docs --> _(no submodules)_- [signaling-client](./src/signaling/signaling-client.md) — Defines the structural contract for any signaling channel used by the WebRTC plugin to exchange offers, answers, and IC…
|
||||
- [websocket-signaling-client](./src/signaling/websocket-signaling-client.md) — Concrete `SignalingClient` implementation backed by a WebSocket. Handles the boring-but-load-bearing pieces of any sign…
|
||||
- [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_ - [transport](./src/transport/transport.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ - [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable error-code registry for the `@suaveplan/network-transport-webrtc` package. All runtime failures with…
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides a WebRTC data-channel transport implementation for the `@suaveplan/network` mesh system. This plugin enables peer-to-peer messaging without requiring a central relay server for data. The transport implements the universal `Transport` interface and is fully mockable for Node.js testing.
|
||||
|
||||
## Features
|
||||
|
||||
- **WebRTCTransport** — full `Transport` interface via `RTCPeerConnection` + `RTCDataChannel`
|
||||
- **Injectable factories** — mockable `PeerConnectionLike` for unit testing
|
||||
- **WebRTCPlugin** — `NetworkPlugin` integration with lifecycle management
|
||||
- **JSON serialization** — uses `@suaveplan/serialize` for envelope encoding
|
||||
- **Peer events** — automatic connected/disconnected notifications
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```ts
|
||||
import { WebRTCPlugin } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const plugin = new WebRTCPlugin();
|
||||
node.use(plugin);
|
||||
await node.start();
|
||||
await plugin.transport.connect("peer-1", "signal-address");
|
||||
```
|
||||
|
||||
### Advanced Example
|
||||
|
||||
```ts
|
||||
import { WebRTCPlugin } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const plugin = new WebRTCPlugin({
|
||||
dataChannelLabel: "mesh",
|
||||
});
|
||||
node.use(plugin);
|
||||
|
||||
plugin.transport.onMessage((peerId, envelope) => {
|
||||
console.log("From", peerId, envelope);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
See per-module documentation in `src/`:
|
||||
|
||||
- `src/transport/` — `WebRTCTransport`
|
||||
- `src/plugin/` — `WebRTCPlugin`
|
||||
- `src/types/` — mockable interfaces and configuration types
|
||||
- `src/error-codes.ts` — `NetworkWebRTCError`, `NETWORK_WEBRTC_CODES`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The transport is designed for testability. In a browser environment, you provide a factory that creates real `RTCPeerConnection` instances. In Node.js tests, you provide mock implementations of `PeerConnectionLike` and `DataChannelLike`. This decouples the transport logic from the browser WebRTC stack, making the package suitable for universal use despite WebRTC being a browser API.
|
||||
@@ -0,0 +1,67 @@
|
||||
# DataChannelManager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Owns the lifecycle of every WebRTC data channel for the mesh transport. Surfaces a single backpressure-aware `send(peerId, bytes)` API and a unified `onMessage` subscriber so the rest of the plugin never touches raw `RTCDataChannel` events. Centralising channel state here means there is exactly one place that decides what "the channel is healthy" means.
|
||||
|
||||
## Features
|
||||
|
||||
- `register(peerId, channel)` and `unregister(peerId)` for explicit lifecycle.
|
||||
- Backpressure: `send` throws `SEND_FAILED` when `bufferedAmount` exceeds `bufferedAmountHighWaterMark` (default 1 MiB).
|
||||
- `PEER_NOT_CONNECTED` and `DATA_CHANNEL_CLOSED` codes for the other failure modes.
|
||||
- Unified `onMessage` subscriber receives raw `Uint8Array` payloads regardless of whether the wire payload was a string or `ArrayBuffer`.
|
||||
- `clear()` for full teardown without per-peer iteration in callers.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { DataChannelManager } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const manager = new DataChannelManager();
|
||||
manager.register("peer-a", channel);
|
||||
manager.onMessage((peerId, bytes) => {
|
||||
console.log("from", peerId, bytes.byteLength, "bytes");
|
||||
});
|
||||
manager.send("peer-a", new Uint8Array([1, 2, 3]));
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { DataChannelManager } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const manager = new DataChannelManager({
|
||||
bufferedAmountHighWaterMark: 256 * 1024,
|
||||
});
|
||||
manager.register("peer-a", busyChannel);
|
||||
|
||||
try {
|
||||
manager.send("peer-a", payload);
|
||||
} catch (err) {
|
||||
if (
|
||||
(err as { code: string }).code === "NET_WEBRTC.SEND_FAILED" &&
|
||||
(err as { context?: { highWaterMark?: number } }).context?.highWaterMark
|
||||
) {
|
||||
// backpressure — drop, queue, or wait for buffered amount to drain
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new DataChannelManager(config?)` — `bufferedAmountHighWaterMark`.
|
||||
- `register(peerId, channel: BackpressureDataChannel)` — installs `onmessage`.
|
||||
- `unregister(peerId)` — clears the handler and forgets the channel.
|
||||
- `has(peerId): boolean`.
|
||||
- `send(peerId, payload: Uint8Array)` — backpressure-aware send; throws on missing/closed/busy.
|
||||
- `onMessage(handler) → () => void` — global inbound subscriber.
|
||||
- `getHighWaterMark(): number`.
|
||||
- `clear()` — unregister every channel and drop subscribers.
|
||||
- `interface BackpressureDataChannel` — `DataChannelLike` + `bufferedAmount`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The manager intentionally does not inspect message ordering or sequence numbers — those concerns belong to higher layers (`network-plugin-protocol`). Backpressure failures are not silently retried; callers that need a queue can wrap `send` in their own envelope and pause on failure. The dispatcher converts string payloads to `Uint8Array` via `TextEncoder` so subscribers always see binary bytes.
|
||||
@@ -0,0 +1,51 @@
|
||||
# WebRTC Error Codes
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the stable error-code registry for the `@suaveplan/network-transport-webrtc` package. All runtime failures within the WebRTC transport layer are tagged with deterministic codes so callers can handle them programmatically. WebRTC is inherently unreliable — connections drop, data channels close unexpectedly, and signaling can fail. Clear error semantics are essential for building resilient mesh networks on top of this transport.
|
||||
|
||||
## Features
|
||||
|
||||
- Deterministic code generation via `defineCodes`
|
||||
- Typed error class extending `SuaveplanError`
|
||||
- Full OpenTelemetry span-event compatibility
|
||||
- Tree-shake safe code definitions
|
||||
- Machine-readable error categorization
|
||||
- Human-readable message keys for i18n
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```ts
|
||||
import { NetworkWebRTCError, NETWORK_WEBRTC_CODES } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
throw new NetworkWebRTCError(
|
||||
NETWORK_WEBRTC_CODES.CONNECTION_FAILED,
|
||||
"webrtc.connection-failed"
|
||||
);
|
||||
```
|
||||
|
||||
### Advanced Example
|
||||
|
||||
```ts
|
||||
import { NetworkWebRTCError, NETWORK_WEBRTC_CODES } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
try {
|
||||
await transport.connect(peerId, address);
|
||||
} catch (err) {
|
||||
if (err instanceof NetworkWebRTCError && err.code === NETWORK_WEBRTC_CODES.CONNECTION_FAILED) {
|
||||
console.log("Connection failed, retrying...");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `NETWORK_WEBRTC_CODES` — frozen registry of WebRTC error codes
|
||||
- `NetworkWebRTCError` — typed error class
|
||||
- `NetworkWebRTCErrorCode` — union type of valid codes
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Codes are namespaced under `NET_WEBRTC` to avoid collisions with other network subsystems. The registry includes codes for connection failures (`CONNECTION_FAILED`), data channel closure (`DATA_CHANNEL_CLOSED`), send failures (`SEND_FAILED`), missing peer connections (`PEER_NOT_CONNECTED`), transport shutdown (`TRANSPORT_CLOSED`), and signaling errors (`SIGNALING_ERROR`). Each code maps to a human-readable message key that can be resolved for internationalization. The `NetworkWebRTCError` class extends `SuaveplanError`, which provides automatic stack trace capture, cause chaining, and OpenTelemetry span event conversion. Error codes are tree-shake safe, meaning unused codes are removed from production bundles by the bundler.
|
||||
@@ -0,0 +1,66 @@
|
||||
# IceManager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Watches the ICE connection state of a single `RTCPeerConnection` and triggers `restartIce()` whenever the state transitions to `failed`. Caps restart attempts so a permanently broken peer cannot loop forever. Holds the configured `iceServers` (STUN + TURN) so callers can construct new peer connections from the same source of truth without copying configuration around.
|
||||
|
||||
## Features
|
||||
|
||||
- WeakMap-backed per-connection restart counter; no leaks across reconnects.
|
||||
- Configurable `maxRestarts` (default 3).
|
||||
- Injectable `IceCapablePeerConnection` shape so the manager works against fakes.
|
||||
- `tryRestart` is the explicit imperative path used by the auto-handler and by callers that want to force a restart on demand.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { IceManager } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const ice = new IceManager({
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||
});
|
||||
ice.attach(peerConnection);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { IceManager, type IceCapablePeerConnection } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const ice = new IceManager({
|
||||
iceServers: [
|
||||
{ urls: "stun:stun.l.google.com:19302" },
|
||||
{
|
||||
urls: ["turn:turn.example.com:3478"],
|
||||
username: "u",
|
||||
credential: "p",
|
||||
},
|
||||
],
|
||||
maxRestarts: 5,
|
||||
});
|
||||
|
||||
ice.attach(peerConnection);
|
||||
|
||||
// Force a restart manually after a network handoff.
|
||||
const restarted = ice.tryRestart(peerConnection);
|
||||
if (!restarted) {
|
||||
// the cap was already reached; tear down and reconnect
|
||||
}
|
||||
|
||||
const attempts = ice.getRestartCount(peerConnection);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new IceManager(config: IceManagerConfig)` — `iceServers`, optional `maxRestarts`.
|
||||
- `attach(connection)` — wires `oniceconnectionstatechange` so `failed` triggers a restart.
|
||||
- `tryRestart(connection): boolean` — returns `true` if restart was attempted, `false` once cap is reached.
|
||||
- `getRestartCount(connection): number` — current attempt count.
|
||||
- `getIceServers(): readonly IceServer[]` — the configured server list, useful when constructing fresh peer connections.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The manager uses `WeakMap` so released `RTCPeerConnection` instances drop their counter without leaking. The `attach`-installed handler unconditionally clears any user-supplied `oniceconnectionstatechange` callback — callers that need to observe state transitions for their own purposes MUST chain off the manager (via the underlying `connection.oniceconnectionstatechange` after `attach`).
|
||||
@@ -0,0 +1,68 @@
|
||||
# WebRTCMesh
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Maintains a full-mesh WebRTC topology on top of `WebRTCTransport`. Owns peer discovery → connect, heartbeat tracking, and stale-peer eviction. Decoupling these from the transport itself means the transport stays a thin wrapper around `RTCPeerConnection` while higher-level mesh concerns (when does a peer count as gone?) live here.
|
||||
|
||||
## Features
|
||||
|
||||
- `addPeer` / `addPeers` for connecting to discovered peers.
|
||||
- Auto-tracks `connected`/`disconnected` transport events into the last-seen map.
|
||||
- `markHeartbeat(peerId)` for liveness updates from heartbeat traffic.
|
||||
- `sweep()` returns the list of evicted peer ids, driven by a caller-owned interval.
|
||||
- Injectable `clock` (default `Date.now`) for deterministic tests.
|
||||
- `getKnownPeers()` and `getLastSeen(peerId)` expose mesh state for observability.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { WebRTCMesh, WebRTCTransport } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const transport = new WebRTCTransport({ peerConnectionFactory });
|
||||
const mesh = new WebRTCMesh({ transport });
|
||||
|
||||
mesh.start();
|
||||
await mesh.addPeers([
|
||||
{ peerId: "alice", address: "..." },
|
||||
{ peerId: "bob", address: "..." },
|
||||
]);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { WebRTCMesh, WebRTCTransport } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
let now = 0;
|
||||
const mesh = new WebRTCMesh({
|
||||
transport,
|
||||
heartbeatTimeoutMs: 5000,
|
||||
clock: () => now,
|
||||
});
|
||||
mesh.start();
|
||||
await mesh.addPeer({ peerId: "alice", address: "..." });
|
||||
|
||||
mesh.markHeartbeat("alice");
|
||||
now += 6000;
|
||||
const evicted = await mesh.sweep();
|
||||
// evicted === ["alice"]
|
||||
|
||||
mesh.stop();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new WebRTCMesh(config)` — `transport`, optional `heartbeatTimeoutMs` (default 30 s) and `clock`.
|
||||
- `start()` / `stop()` — wire/unwire transport peer-event subscription.
|
||||
- `addPeer(peer)` / `addPeers(peers)` — connect through the underlying transport.
|
||||
- `markHeartbeat(peerId)` — record liveness.
|
||||
- `removePeer(peerId)` — disconnect and forget.
|
||||
- `sweep(): Promise<readonly PeerId[]>` — evict every peer past the heartbeat timeout, returns evicted ids.
|
||||
- `getKnownPeers()`, `getLastSeen(peerId)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`sweep()` performs the disconnect serially so failures bubble up directly; if a transport disconnect rejects, the sweep aborts (the caller can retry on the next interval). The mesh does not own its own timer — it expects the consumer (`WebRTCPlugin` or a test harness) to drive `sweep()` so the cadence stays explicit and testable. Heartbeat semantics (frame layout, frequency) live in `PingManager`; this class only consumes the resulting timestamps.
|
||||
@@ -0,0 +1,67 @@
|
||||
# PingManager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Tracks correlated echo frames between peers and computes EMA-smoothed RTTs. The manager is transport-agnostic — callers feed it `recordSent`/`recordReceived` events with a stable `pingId` correlator and the manager handles smoothing, in-flight cleanup, and `peer:latency` bus emission.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-peer `LatencyRecord` with `lastRttMs`, EMA-smoothed `avgRttMs`, and `samples` count.
|
||||
- Configurable EMA window (default 10) → `α = 2 / (window + 1)`.
|
||||
- Bus emission on every successful pong via `peer:latency`.
|
||||
- Late-arrival safety: pongs whose `pingId` was never sent are silently ignored (return `undefined`).
|
||||
- Negative RTT clamping: any clock skew that would yield negative RTT is floored at 0.
|
||||
- `reset(peerId)` for forgetting one peer's history; `clear()` for full teardown.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { PingManager } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const ping = new PingManager({ bus });
|
||||
ping.recordSent("alice", "ping-1");
|
||||
// ...later, on pong receive:
|
||||
ping.recordReceived("alice", "ping-1");
|
||||
const r = ping.getRecord("alice");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { PingManager } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
let now = 0;
|
||||
const ping = new PingManager({
|
||||
bus,
|
||||
emaWindow: 5,
|
||||
clock: () => now,
|
||||
});
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const id = `p${i}`;
|
||||
ping.recordSent("alice", id);
|
||||
now += 50 + (i % 2 === 0 ? 0 : 20);
|
||||
ping.recordReceived("alice", id);
|
||||
}
|
||||
|
||||
const record = ping.getRecord("alice")!;
|
||||
// record.avgRttMs has converged toward ~60 ms
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new PingManager({ bus, emaWindow?, clock? })`.
|
||||
- `recordSent(peerId, pingId)` — note the outbound ping.
|
||||
- `recordReceived(peerId, pingId): number | undefined` — returns raw RTT, or `undefined` if the id was never sent.
|
||||
- `getRecord(peerId): LatencyRecord | undefined`.
|
||||
- `reset(peerId)` — drops in-flight pings + record for one peer.
|
||||
- `clear()` — full teardown.
|
||||
- `interface PingBus { emit("peer:latency", { peerId, rttMs }) }`.
|
||||
- `interface LatencyRecord { lastRttMs, avgRttMs, samples }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The manager keeps in-flight pings in a `Map` keyed by `${peerId}::${pingId}` so two peers can use the same ping id without colliding. The bus payload uses the _smoothed_ `avgRttMs` so subscribers see a stable trend rather than per-sample noise — callers that need raw RTT can pull it from the `recordReceived` return value or `getRecord(peerId).lastRttMs`. EMA initialisation seeds the average to the first sample so a fresh peer does not need a long warm-up before the value reflects reality.
|
||||
@@ -0,0 +1,52 @@
|
||||
# WebRTCPlugin
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Integrates `WebRTCTransport` into a `NetworkNode` as a `NetworkPlugin`. This plugin exposes the WebRTC transport as a public property and manages its lifecycle according to the node's start/stop/dispose sequence.
|
||||
|
||||
## Features
|
||||
|
||||
- Implements `NetworkPlugin` for automatic lifecycle management
|
||||
- Exposes `WebRTCTransport` as a public readonly property
|
||||
- Clean shutdown via `stop()` and `dispose()`
|
||||
- Configurable via `WebRTCConfig`
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```ts
|
||||
import { WebRTCPlugin } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const plugin = new WebRTCPlugin();
|
||||
node.use(plugin);
|
||||
```
|
||||
|
||||
### Advanced Example
|
||||
|
||||
```ts
|
||||
import { WebRTCPlugin } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const plugin = new WebRTCPlugin({
|
||||
dataChannelLabel: "custom",
|
||||
});
|
||||
node.use(plugin);
|
||||
await node.start();
|
||||
await plugin.transport.connect("peer-1", "signal");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `name` — always `"webrtc"`
|
||||
- `transport` — `WebRTCTransport` instance
|
||||
- `install(node)` — store node reference and register the transport with the node
|
||||
- `start()` — no-op (transport is ready)
|
||||
- `stop()` — close transport and unregister it from the node
|
||||
- `dispose()` — release node reference
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The plugin creates the transport eagerly in the constructor so callers can configure it before the node starts. The `start()` method is intentionally a no-op because the transport does not automatically open connections — callers must explicitly invoke `transport.connect()`. This gives applications full control over when and to whom connections are established. The `stop()` method closes all active connections and cleans up resources. `dispose()` releases any internal references to prevent memory leaks during long-running application shutdown. Because the plugin does not store a reference to the node, it can be safely reused across multiple node instances as long as a fresh plugin instance is created for each node. The transport property is public and readonly, allowing external code to inspect its state or register event handlers directly without going through the plugin abstraction.
|
||||
@@ -0,0 +1,82 @@
|
||||
# SignalingClient
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the structural contract for any signaling channel used by the WebRTC plugin to exchange offers, answers, and ICE candidates between peers. The transport is intentionally pluggable: WebRTC does not specify how peers exchange SDP, so this package abstracts that entirely. Consumers can wire a WebSocket signaling server, a pubsub channel, or any custom relay without changing the rest of the plugin.
|
||||
|
||||
## Features
|
||||
|
||||
- Strict `SignalType` enum (`offer` / `answer` / `ice-candidate`).
|
||||
- `SignalingMessage` shape with explicit `from` / `to` peer IDs and a UTF-8 `payload` string holding the SDP or candidate JSON.
|
||||
- `SignalingHandler` subscription model with unsubscribe-returning `onMessage`.
|
||||
- Lifecycle: explicit `connect()` / `close()` plus an `isOpen` getter so callers can see the wire state.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { SignalingClient } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
declare const client: SignalingClient;
|
||||
|
||||
await client.connect();
|
||||
const off = client.onMessage((msg) => {
|
||||
if (msg.type === "offer") {
|
||||
// hand the SDP to your peer-connection
|
||||
}
|
||||
});
|
||||
await client.send({
|
||||
type: "offer",
|
||||
from: "peer-a",
|
||||
to: "peer-b",
|
||||
payload: JSON.stringify({ sdp: "..." }),
|
||||
});
|
||||
off();
|
||||
await client.close();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { SignalingClient, SignalingMessage } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
class InMemorySignalingClient implements SignalingClient {
|
||||
private handlers = new Set<(m: SignalingMessage) => void>();
|
||||
public isOpen = false;
|
||||
|
||||
constructor(private readonly bus: { emit(m: SignalingMessage): void; on(fn: (m: SignalingMessage) => void): () => void }) {}
|
||||
|
||||
public connect(): void {
|
||||
this.isOpen = true;
|
||||
}
|
||||
public close(): void {
|
||||
this.isOpen = false;
|
||||
this.handlers.clear();
|
||||
}
|
||||
public send(message: SignalingMessage): void {
|
||||
this.bus.emit(message);
|
||||
}
|
||||
public onMessage(handler: (m: SignalingMessage) => void): () => void {
|
||||
this.handlers.add(handler);
|
||||
const off = this.bus.on((m) => {
|
||||
for (const h of this.handlers) h(m);
|
||||
});
|
||||
return () => {
|
||||
this.handlers.delete(handler);
|
||||
off();
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `type SignalType = "offer" | "answer" | "ice-candidate"`.
|
||||
- `interface SignalingMessage { type, from, to, payload }`.
|
||||
- `type SignalingHandler = (message: SignalingMessage) => void`.
|
||||
- `interface SignalingClient { send, onMessage, connect, close, isOpen }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The contract intentionally does not include peer identity verification. Callers that need authenticated signaling MUST sign or wrap messages at a higher layer (e.g. via `network-plugin-crypto-messages`). Errors during `send`/`connect` MUST be raised as `NetworkWebRTCError` so the rest of the plugin treats them uniformly.
|
||||
|
||||
The `payload` is typed as `string` rather than a structured SDP object so the contract stays schema-agnostic — the wrapping protocol (raw SDP JSON, length-prefixed CBOR, etc.) is an implementation detail of the chosen relay. Implementations MAY choose to expose the underlying socket/channel via a sibling property if observability tooling needs raw frame counts, but doing so is explicitly outside the contract surface and consumers MUST NOT rely on it for correctness. The `isOpen` getter is a read-only view of the live wire state — a `true` reading is best-effort because the underlying socket can fail at any moment between checking `isOpen` and a subsequent `send`. Callers that care about strict-once delivery MUST treat `send` failures as the source of truth and retry through the upper-layer protocol, not by polling `isOpen`.
|
||||
@@ -0,0 +1,92 @@
|
||||
# WebSocketSignalingClient
|
||||
|
||||
## Purpose
|
||||
|
||||
Concrete `SignalingClient` implementation backed by a WebSocket. Handles the boring-but-load-bearing pieces of any signaling rig: open lifecycle, JSON envelope encode/decode, error surfacing, and reconnection with a hard attempt cap so a flaky relay does not spin forever. The WebSocket itself is injected through a factory so tests can swap in any structurally-compatible mock.
|
||||
|
||||
## Features
|
||||
|
||||
- Promise-based `connect()` resolves on first `onopen` and rejects on early `onerror`.
|
||||
- Configurable `reconnectDelayMs` (default 1000 ms) and `maxReconnects` (default 5).
|
||||
- Auto-reconnect on involuntary close; honours `close()` as an explicit teardown that suppresses reconnects.
|
||||
- JSON envelope encode/decode with silent drop on malformed messages.
|
||||
- `WebSocketFactory` injection for deterministic tests.
|
||||
- Throws `SIGNALING_ERROR` on `send` while not open, and on `connect` when the socket errors before opening.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { WebSocketSignalingClient } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const client = new WebSocketSignalingClient({
|
||||
url: "wss://signaling.example.com/mesh",
|
||||
webSocketFactory: (url) => new WebSocket(url) as never,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
const off = client.onMessage((msg) => {
|
||||
console.log(msg.type, msg.from, "→", msg.to);
|
||||
});
|
||||
client.send({
|
||||
type: "offer",
|
||||
from: "alice",
|
||||
to: "bob",
|
||||
payload: JSON.stringify({ sdp: "..." }),
|
||||
});
|
||||
off();
|
||||
client.close();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
WebSocketSignalingClient,
|
||||
type WebSocketLike,
|
||||
} from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
class FakeSocket implements WebSocketLike {
|
||||
public readyState = 1;
|
||||
public onopen: WebSocketLike["onopen"] = null;
|
||||
public onmessage: WebSocketLike["onmessage"] = null;
|
||||
public onclose: WebSocketLike["onclose"] = null;
|
||||
public onerror: WebSocketLike["onerror"] = null;
|
||||
public sent: string[] = [];
|
||||
public send(data: string): void {
|
||||
this.sent.push(data);
|
||||
}
|
||||
public close(): void {
|
||||
this.onclose?.call(this, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const sockets: FakeSocket[] = [];
|
||||
const client = new WebSocketSignalingClient({
|
||||
url: "ws://test",
|
||||
webSocketFactory: () => {
|
||||
const s = new FakeSocket();
|
||||
sockets.push(s);
|
||||
queueMicrotask(() => s.onopen?.call(s, undefined));
|
||||
return s;
|
||||
},
|
||||
reconnectDelayMs: 5,
|
||||
maxReconnects: 2,
|
||||
});
|
||||
await client.connect();
|
||||
sockets[0]!.close();
|
||||
// reconnects up to 2 times after 5 ms each
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new WebSocketSignalingClient(config: WebSocketSignalingConfig)` — `url`, `webSocketFactory`, optional `reconnectDelayMs`, `maxReconnects`.
|
||||
- `connect(): Promise<void>` — opens the socket; resolves on `onopen`.
|
||||
- `close(): void` — explicit teardown, cancels pending reconnects.
|
||||
- `send(message)` — JSON-encodes and writes to the socket; throws `NET_WEBRTC.SIGNALING_ERROR` if not open.
|
||||
- `onMessage(handler) → () => void` — register/unregister inbound handler.
|
||||
- `isOpen: boolean` — true iff `readyState === 1`.
|
||||
- `interface WebSocketLike` and `type WebSocketFactory` for injection.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
A failed reconnect chains via the `onclose` handler: `openOnce` rejection triggers another `scheduleReconnect`. The total number of reconnect attempts is bounded by `maxReconnects`. The reconnect timer is cancelled on explicit `close` so callers do not see ghost connection attempts after teardown. Malformed payloads are dropped silently because signaling is best-effort and the source SDP can always be retried by the higher layer.
|
||||
@@ -0,0 +1,63 @@
|
||||
# WebRTCTransport
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Implements the `Transport` interface from `@suaveplan/network` using WebRTC data channels. This transport enables peer-to-peer messaging in mesh networks without requiring a central server for data relay. It wraps `RTCPeerConnection` and `RTCDataChannel` with a mockable abstraction so it can be fully tested in Node.js.
|
||||
|
||||
## Features
|
||||
|
||||
- Full `Transport` interface implementation
|
||||
- Injectable `PeerConnectionLike` factory for testability
|
||||
- JSON serialization via `@suaveplan/serialize`
|
||||
- Automatic peer event emission (connected/disconnected)
|
||||
- Graceful close with cleanup
|
||||
- Idempotent connect
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```ts
|
||||
import { WebRTCTransport } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const transport = new WebRTCTransport();
|
||||
await transport.connect("peer-1", "signal-addr");
|
||||
```
|
||||
|
||||
### Advanced Example
|
||||
|
||||
```ts
|
||||
import { WebRTCTransport } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const transport = new WebRTCTransport({
|
||||
peerConnectionFactory: (peerId) => new RTCPeerConnection({ iceServers: [] }),
|
||||
dataChannelLabel: "mesh",
|
||||
});
|
||||
|
||||
transport.onMessage((peerId, envelope) => {
|
||||
console.log("Received from", peerId, envelope);
|
||||
});
|
||||
|
||||
transport.onPeerEvent((peerId, event) => {
|
||||
console.log(peerId, event);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `constructor(config?)` — create transport with optional factory and label
|
||||
- `id` — always `"webrtc"`
|
||||
- `connect(peerId, address)` — initiate connection
|
||||
- `disconnect(peerId)` — close specific peer connection
|
||||
- `send(peerId, envelope)` — send envelope to peer
|
||||
- `onMessage(handler)` — subscribe to messages
|
||||
- `onPeerEvent(handler)` — subscribe to connect/disconnect events
|
||||
- `getConnectedPeers()` — list peers with open data channels
|
||||
- `close()` — shut down all connections
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The transport maintains an internal `Map` of peer connections. Each connection has a data channel that is created immediately during `connect()`. The transport listens for `onopen`, `onmessage`, `onclose`, and `onerror` events on each channel. Messages are serialized to JSON strings using `@suaveplan/serialize` before sending, and deserialized on receipt. Malformed messages are silently dropped to prevent a single bad peer from crashing the transport. The `close()` method iterates all connections, closes channels and connections, and clears all internal state.
|
||||
@@ -0,0 +1,55 @@
|
||||
# WebRTC Types
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides the type definitions and interfaces used by the WebRTC transport plugin. These types are designed to be mockable so the transport can be fully unit-tested in Node.js without requiring a real browser WebRTC implementation.
|
||||
|
||||
## Features
|
||||
|
||||
- Mockable `PeerConnectionLike` interface
|
||||
- Mockable `DataChannelLike` interface
|
||||
- `PeerConnectionFactory` for injectable construction
|
||||
- `WebRTCConfig` for transport configuration
|
||||
- Internal `PeerConnectionState` for tracking active connections
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```ts
|
||||
import type { WebRTCConfig } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const config: WebRTCConfig = {
|
||||
dataChannelLabel: "mesh",
|
||||
};
|
||||
```
|
||||
|
||||
### Advanced Example
|
||||
|
||||
```ts
|
||||
import type { PeerConnectionLike, DataChannelLike } from "@suaveplan/network-transport-webrtc";
|
||||
|
||||
const mockConnection: PeerConnectionLike = {
|
||||
createDataChannel: () => mockChannel,
|
||||
close: () => {},
|
||||
connectionState: "connected",
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `PeerConnectionLike` — minimal RTCPeerConnection interface
|
||||
- `DataChannelLike` — minimal RTCDataChannel interface
|
||||
- `MessageEventLike` — minimal MessageEvent interface
|
||||
- `PeerConnectionFactory` — factory function signature
|
||||
- `WebRTCConfig` — transport configuration object
|
||||
- `PeerConnectionState` — internal connection tracking
|
||||
- `MessageHandler` — incoming message callback type
|
||||
- `PeerEventHandler` — peer event callback type
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The interfaces are intentionally minimal — they include only the methods and properties required by `WebRTCTransport`. This keeps the mock surface area small and makes tests easy to write. The real browser `RTCPeerConnection` and `RTCDataChannel` objects implement many more properties, but the transport only needs a subset. If you need to use the transport in a browser, you can pass the native constructors directly via a factory adapter. The `PeerConnectionFactory` receives the target `PeerId` as an argument so the factory can create connection-specific configurations (for example, setting a custom ICE server per peer). The `WebRTCConfig` object is fully serializable and contains no functions by default, making it easy to store or transmit configuration templates. The `PeerConnectionState` interface is used internally by the transport and is exposed for advanced use cases such as connection monitoring or custom signaling implementations.
|
||||
Reference in New Issue
Block a user