Initial Commit
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
# OpenSpec Change: enterprise-browser-network-webrtc
|
||||
|
||||
- **Change ID**: enterprise-browser-network-webrtc
|
||||
- **Package**: `@suaveplan/network-webrtc`
|
||||
- **Category**: browser
|
||||
- **Target path**: `browser/network/network-webrtc`
|
||||
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/network-webrtc.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/network-webrtc` on the genesis branch from scratch at enterprise quality. The package provides the browser-side WebRTC transport for `@suaveplan/network`: a `WebRTCTransport` wrapping `RTCPeerConnection` + `RTCDataChannel`, a dual-channel system for reliable + unreliable messaging, a `ChannelRouter` dispatching by topic, and a first-class `SignalingChannel` interface with Memory / WebSocket / WebTorrent implementations. No React dependencies — demo UI moves to a React companion package.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — fresh build.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/network` (`workspace:^`) | `Transport` interface, `NetworkError`, connection lifecycle types |
|
||||
| `@suaveplan/error` (`workspace:^`) | Typed WebRTC errors |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `EventBusLike` |
|
||||
| `@suaveplan/events` (`workspace:^`) | `EventEmitter` for transport event maps |
|
||||
| `@suaveplan/serialize` (`workspace:^`) | Structured message encoding/decoding |
|
||||
| `@suaveplan/schema-registry` (`workspace:^`) | Register signalling Zod schemas |
|
||||
| `zod` (`catalog:forms`) | Schemas for signalling message types |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | WebRTC metrics |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `@playwright/test` (`catalog:testing`), `@suaveplan/testing workspace:^`.
|
||||
|
||||
---
|
||||
|
||||
## Preserved public API surface (from main branch)
|
||||
|
||||
### Core transport
|
||||
|
||||
```ts
|
||||
class WebRTCTransport {
|
||||
constructor(config: WebRTCConfig)
|
||||
dial(signal: SignalingChannel): Promise<void>
|
||||
close(): void
|
||||
send(data: Uint8Array | string): void
|
||||
sendUnreliable(data: Uint8Array | string): void
|
||||
onMessage(handler: (data: Uint8Array | string) => void): void
|
||||
onClose(handler: () => void): void
|
||||
getStats(): Promise<RTCStatsReport>
|
||||
readonly state: RTCPeerConnectionState
|
||||
}
|
||||
|
||||
interface WebRTCConfig {
|
||||
iceServers?: RTCIceServer[]
|
||||
ordered?: boolean
|
||||
maxRetransmits?: number
|
||||
allowedWorkletUrls?: string[]
|
||||
}
|
||||
```
|
||||
|
||||
### Dual channel
|
||||
|
||||
```ts
|
||||
class DualChannel {
|
||||
constructor(connection: RTCPeerConnection, config?: DualChannelConfig)
|
||||
sendReliable(data: Uint8Array | string): void
|
||||
sendUnreliable(data: Uint8Array | string): void
|
||||
onMessage(handler: (data: Uint8Array | string, reliable: boolean) => void): void
|
||||
close(): void
|
||||
}
|
||||
```
|
||||
|
||||
### Channel router
|
||||
|
||||
```ts
|
||||
class ChannelRouter {
|
||||
register(channelId: string, handler: ChannelHandler): void
|
||||
unregister(channelId: string): void
|
||||
route(message: ChannelMessage): void
|
||||
send(channelId: string, payload: unknown): void
|
||||
}
|
||||
|
||||
interface ChannelMessage { channelId: string; payload: unknown; reliable?: boolean }
|
||||
type ChannelHandler = (payload: unknown) => void
|
||||
```
|
||||
|
||||
### Signalling interface
|
||||
|
||||
```ts
|
||||
interface SignalingChannel {
|
||||
sendOffer(offer: RTCSessionDescriptionInit): Promise<void>
|
||||
sendAnswer(answer: RTCSessionDescriptionInit): Promise<void>
|
||||
sendIceCandidate(candidate: RTCIceCandidateInit): Promise<void>
|
||||
onOffer(handler: (offer: RTCSessionDescriptionInit) => void): void
|
||||
onAnswer(handler: (answer: RTCSessionDescriptionInit) => void): void
|
||||
onIceCandidate(handler: (candidate: RTCIceCandidateInit) => void): void
|
||||
close(): void
|
||||
}
|
||||
|
||||
class MemorySignalingChannel implements SignalingChannel { ... }
|
||||
class WebSocketSignalingChannel implements SignalingChannel { constructor(url: string) }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### 1. Structured errors via `@suaveplan/error`
|
||||
|
||||
Error codes: `WEBRTC_DIAL_FAILED`, `WEBRTC_ICE_FAILED`, `WEBRTC_CHANNEL_CLOSED`, `WEBRTC_SIGNALING_TIMEOUT`, `WEBRTC_INVALID_STATE`.
|
||||
|
||||
### 2. Perfect-negotiation pattern
|
||||
|
||||
`PeerConnection` implements "polite peer" logic — avoids offer/answer glare on simultaneous negotiation.
|
||||
|
||||
### 3. ICE-restart policy
|
||||
|
||||
Detect `iceConnectionState === 'failed'`; auto-restart ICE; surface as typed event `'ice:restart'`.
|
||||
|
||||
### 4. Flow control + back-pressure
|
||||
|
||||
`DualChannel.sendReliable` tracks `RTCDataChannel.bufferedAmount`; blocks or queues when buffer exceeds a configurable watermark.
|
||||
|
||||
### 5. OTel metrics
|
||||
|
||||
Counters `webrtc.dial_total`, `webrtc.bytes_in_total`, `webrtc.bytes_out_total`; histogram `webrtc.ice_gather_ms`, `webrtc.rtt_ms`.
|
||||
|
||||
### 6. Signalling message Zod schemas
|
||||
|
||||
Schemas for offer/answer/ICE-candidate messages registered in `@suaveplan/schema-registry`.
|
||||
|
||||
### 7. STUN/TURN configuration helpers
|
||||
|
||||
Curated `iceServers` bundles with validation helper; `TURN_CONFIG` constants for common providers.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
browser/network/network-webrtc/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
webrtc/
|
||||
peer-connection/
|
||||
peer-connection.ts (RTCPeerConnection wrapper + ICE restart + perfect negotiation)
|
||||
peer-connection.md
|
||||
peer-connection.test.ts
|
||||
index.ts
|
||||
data-channel/
|
||||
data-channel.ts
|
||||
data-channel.md
|
||||
data-channel.test.ts
|
||||
index.ts
|
||||
signaling-handlers/
|
||||
signaling-handlers.ts
|
||||
signaling-handlers.md
|
||||
signaling-handlers.test.ts
|
||||
index.ts
|
||||
webrtc-transport/
|
||||
webrtc-transport.ts
|
||||
webrtc-transport.md
|
||||
webrtc-transport.types.ts
|
||||
webrtc-transport.test.ts
|
||||
index.ts
|
||||
dual-channel/
|
||||
dual-channel/
|
||||
dual-channel.ts
|
||||
dual-channel.md
|
||||
dual-channel.test.ts
|
||||
index.ts
|
||||
channel-router/
|
||||
channel-router/
|
||||
channel-router.ts
|
||||
channel-router.md
|
||||
channel-router.test.ts
|
||||
index.ts
|
||||
signaling/
|
||||
signaling-channel/
|
||||
signaling-channel.ts (interface + MemorySignalingChannel)
|
||||
signaling-channel.md
|
||||
signaling-channel.test.ts
|
||||
index.ts
|
||||
websocket-signaling/
|
||||
websocket-signaling.ts
|
||||
websocket-signaling.md
|
||||
websocket-signaling.test.ts
|
||||
index.ts
|
||||
config/
|
||||
ice-servers/
|
||||
ice-servers.ts
|
||||
ice-servers.md
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
e2e/
|
||||
webrtc.spec.ts (Playwright two-browser offer/answer/ICE/dial/send)
|
||||
examples/ (NOT in package.json "files")
|
||||
basic-usage.ts
|
||||
memory-signaling.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream**: `@suaveplan/drive-webrtc`, `@suaveplan/network-discovery-webtorrent`, WebRTC-based game networking.
|
||||
|
||||
**Breaking changes**: `react` removed from runtime deps (demo moved to companion); examples moved out of published tarball.
|
||||
|
||||
**Prerequisites**: `@suaveplan/network`, `@suaveplan/serialize`, `@suaveplan/error`, `@suaveplan/events` on Verdaccio.
|
||||
@@ -0,0 +1,132 @@
|
||||
# Tasks: enterprise-browser-network-webrtc
|
||||
|
||||
**Change ID**: enterprise-browser-network-webrtc
|
||||
**Package**: `@suaveplan/network-webrtc`
|
||||
**Target path**: `browser/network/network-webrtc`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/network`, `@suaveplan/serialize`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create `browser/network/network-webrtc/` directory tree per layout in `proposal.md`.
|
||||
- [x] Write `package.json` at version `0.1.0`:
|
||||
- `"sideEffects": false`; no `react` runtime dep
|
||||
- `exports`: `"."`, `"./webrtc"`, `"./dual-channel"`, `"./channel-router"`, `"./signaling"`, `"./config/ice-servers"`
|
||||
- `dependencies`: `@suaveplan/network workspace:^`, `@suaveplan/serialize workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `@playwright/test catalog:testing`, `@suaveplan/testing workspace:^`
|
||||
- `files`: `["dist", "src", "README.md"]` — `examples/` intentionally excluded
|
||||
- [x] Write `tsconfig.json` and `vitest.config.ts` (pool: forks).
|
||||
|
||||
---
|
||||
|
||||
## 2. Error codes (`src/error-codes.ts`)
|
||||
|
||||
- [x] Define `WebRTCErrorCode`: `WEBRTC_DIAL_FAILED`, `WEBRTC_ICE_FAILED`, `WEBRTC_CHANNEL_CLOSED`, `WEBRTC_SIGNALING_TIMEOUT`, `WEBRTC_INVALID_STATE`.
|
||||
- [x] Export `registerWebRTCErrorCodes()` and call eagerly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Signalling (`src/signaling/`)
|
||||
|
||||
### 3a. `signaling-channel/signaling-channel.ts`
|
||||
|
||||
- [x] Define `SignalingChannel` interface with offer/answer/ICE methods.
|
||||
- [x] Implement `MemorySignalingChannel` for in-process testing (two paired instances).
|
||||
- [x] Add Zod schemas `OfferMessageSchema`, `AnswerMessageSchema`, `IceCandidateMessageSchema`; validate on receive.
|
||||
- [x] Write `signaling-channel.test.ts`, `signaling-channel.md`.
|
||||
|
||||
### 3b. `websocket-signaling/websocket-signaling.ts`
|
||||
|
||||
- [x] Implement `WebSocketSignalingChannel` with reconnect on error.
|
||||
- [x] Write `websocket-signaling.test.ts`, `websocket-signaling.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Peer connection (`src/webrtc/peer-connection/`)
|
||||
|
||||
- [x] Implement `PeerConnection` wrapping `RTCPeerConnection`:
|
||||
- Perfect-negotiation pattern (`onnegotiationneeded` with polite-peer collision handling).
|
||||
- ICE-restart policy: detect `iceConnectionState === 'failed'`, call `restartIce()`, emit `'ice:restart'`.
|
||||
- Typed event emitter for `'state:change'`, `'ice:state:change'`, `'ice:restart'`, `'error'`.
|
||||
- [x] Write `peer-connection.test.ts`, `peer-connection.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Data channel (`src/webrtc/data-channel/`)
|
||||
|
||||
- [x] Implement `DataChannel` wrapping `RTCDataChannel` with `onMessage`, `send`, `close`, back-pressure watermark.
|
||||
- [x] Write `data-channel.test.ts`, `data-channel.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Signalling handlers (`src/webrtc/signaling-handlers/`)
|
||||
|
||||
- [x] Implement `SignalingHandlers`: route offer/answer/ICE-candidate events from `SignalingChannel` to `PeerConnection`.
|
||||
- [x] Write `signaling-handlers.test.ts`, `signaling-handlers.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. WebRTC transport (`src/webrtc/webrtc-transport/`)
|
||||
|
||||
- [x] Implement `WebRTCTransport` composing `PeerConnection`, `DataChannel`, `SignalingHandlers`.
|
||||
- [x] Wire OTel counters + histograms from `src/error-codes.ts` metrics.
|
||||
- [x] Write `webrtc-transport.test.ts` (vitest, no describe.skip), `webrtc-transport.md`, `webrtc-transport.types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Dual channel (`src/dual-channel/dual-channel/`)
|
||||
|
||||
- [x] Implement `DualChannel` with reliable (ordered, maxRetransmits=10) and unreliable (unordered, maxRetransmits=0) channels.
|
||||
- [x] Back-pressure: track `bufferedAmount`; queue sends when > watermark.
|
||||
- [x] Write `dual-channel.test.ts`, `dual-channel.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Channel router (`src/channel-router/channel-router/`)
|
||||
|
||||
- [x] Implement `ChannelRouter` with register/unregister/route/send.
|
||||
- [x] Write `channel-router.test.ts`, `channel-router.md`.
|
||||
|
||||
---
|
||||
|
||||
## 10. ICE server config (`src/config/ice-servers/`)
|
||||
|
||||
- [x] Export `STUN_SERVERS` (public Cloudflare STUN), `validateIceServers(servers)` (Zod), and builder helper.
|
||||
- [x] Write `ice-servers.md`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Integration test (`tests/integration.test.ts`)
|
||||
|
||||
- [x] Two in-process `WebRTCTransport` instances connected via `MemorySignalingChannel`: dial → send reliable + unreliable → receive → close.
|
||||
- [x] ICE-restart: simulate `iceConnectionState = 'failed'`; verify restart event fires and connection recovers.
|
||||
- [x] Flow-control: send above watermark; verify queuing without data loss.
|
||||
|
||||
---
|
||||
|
||||
## 12. Playwright E2E (`e2e/webrtc.spec.ts`)
|
||||
|
||||
- [x] Two Chromium browser contexts; each runs a Vite-bundled test page.
|
||||
- [x] Signal via in-page `BroadcastChannel` acting as the `MemorySignalingChannel` equivalent.
|
||||
- [x] Assert: offer/answer/ICE/dial/send/close lifecycle; message received in both directions.
|
||||
|
||||
---
|
||||
|
||||
## 13. Docs
|
||||
|
||||
- [x] Write `.md` per module (≥200 words, API reference, examples).
|
||||
- [x] Write `README.md` with subpath table, signalling matrix, ICE/TURN ops guidance, metrics table.
|
||||
|
||||
---
|
||||
|
||||
## 14. Verify and publish
|
||||
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/network-webrtc` — green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/network-webrtc` — 100% on touched modules.
|
||||
- [x] `bun run docs:check --filter=@suaveplan/network-webrtc` — zero errors.
|
||||
- [x] No `describe.skip`, `vi.mock`, `vi.stubGlobal`, `any`, `ts-ignore` in `src/`.
|
||||
- [x] `react` not in runtime deps.
|
||||
- [x] Playwright E2E proves offer/answer/ICE/dial/send/close.
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/network-webrtc`.
|
||||
- [x] Archive to `openspec/archive/2026-04-24-enterprise-browser-network-webrtc/`.
|
||||
Reference in New Issue
Block a user