5.7 KiB
OpenSpec Change: network-transport-websockets-adapter
- Change ID: network-transport-websockets-adapter
- Package:
@suaveplan/network-transport-websockets(already exists at0.1.0) - Category: universal
- Target path:
packages/network/network-transport-websockets(in place) - Status: PROPOSED
- Date: 2026-05-11
- Parent:
network-transport-family(2026-05-11) — Wave 1, audit finding 2.3
Summary
Refactor @suaveplan/network-transport-websockets from its current client-server WebSocketClient shape into a peer-oriented Transport implementation registered as a NetworkPlugin. The package was migrated to genesis before the Transport contract from @suaveplan/network ✅ existed; today it exposes a connect-to-a-server abstraction (typed framing, heartbeat, backpressure queue, adapters) but does NOT implement connect(peerId) / disconnect(peerId) / send(peerId, envelope) / onMessage / getConnectedPeers / close. This change adds a WebSocketTransport + WebSocketPlugin layer on top of the existing client surface so the package can plug into network-plugin-transport-manager ✅ alongside network-transport-webrtc ✅.
Why this is needed
Per the audit finding recorded in openspec/changes/network-transport-family/tasks.md §2.3:
network-transport-websocketspredates theTransportcontract and exposes a client-serverWebSocketClientrather than a peer transport. Wiring it as aNetworkPluginrequires a non-trivial refactor (per-peer connections,getConnectedPeers()aggregation,transport:connected/disconnectedbus emission). Deferred to a dedicated child proposal.
This is that proposal. Three downstream consumers are blocked until it lands:
network-plugin-transport-manager✅ — cannot register WS as a fallback transport without the adapter.network-transport-webrtc✅ — needs WS as a fallback in its signaling channel.websocket-gateway(Wave 5) — server-side rooms + presence depends on the transport contract being honoured.
What changes
New code
src/transport/transport.ts—WebSocketTransport implements Transport(from@suaveplan/network)connect(peerId, address)→ creates a newWebSocketClientkeyed bypeerIddisconnect(peerId)→ closes the per-peer clientsend(peerId, envelope)→ delegates to the per-peer client's framed sendonMessage(handler)→ fan-in across every per-peer clientgetConnectedPeers()→ snapshot of keysclose()→ closes every per-peer client
src/plugin/plugin.ts—WebSocketPlugin implements NetworkPluginmirroringWebRTCPluginshape:install(node)→node.registerTransport(this.transport)start()→ no-op (lazy connect)stop()→ close transport + unregisterdispose()→ idempotent cleanup
src/transport/transport.spec-conformance.test.ts— runs theTransportcontract conformance suitesrc/plugin/plugin.spec-conformance.test.ts— runs theNetworkPluginlifecycle conformance suite- Sibling
.mddocs for both modules
Existing code
src/core/client/— keep. The newWebSocketTransportcomposes per-peerWebSocketClientinstances.src/core/queue/+src/core/heartbeat/— keep, consumed per-peer.src/adapters/— keep, injectable through the new transport.src/types/— extend withWebSocketTransportConfig(per-peer client config + transport-level limits).
Error codes
- Extend the existing
defineCodes("NET_WS", [...])registry with:PEER_ALREADY_CONNECTED,PEER_UNKNOWN,TRANSPORT_CLOSED,MAX_PEERS_EXCEEDED.
Observability
- Spans:
ws_transport.connect,ws_transport.disconnect,ws_transport.send. - Counters:
ws_transport.peers_connected{outcome},ws_transport.messages_total{direction,outcome}. - Gauges:
ws_transport.active_peers.
Parity Checklist
Migration from upstream is already complete; this is a refactor adding the Transport adapter. The original WebSocketClient API is preserved.
| Capability | Before | After | Notes |
|---|---|---|---|
WebSocketClient typed framing |
✅ | ✅ | Unchanged. |
| Heartbeat / ping-pong | ✅ | ✅ | Unchanged; runs per-peer. |
| Backpressure queue | ✅ | ✅ | Unchanged; runs per-peer. |
| Injectable transport adapters | ✅ | ✅ | Unchanged. |
Transport contract |
🚫 | ⬆️ | NEW — WebSocketTransport. |
NetworkPlugin lifecycle |
🚫 | ⬆️ | NEW — WebSocketPlugin. |
| Multi-peer fan-in/fan-out | 🚫 | ⬆️ | NEW — per-peer client registry. |
transport:connected/disconnected bus events |
🚫 | ⬆️ | NEW — on bus when a NetworkBus is provided via plugin. |
| Backward compatibility | ✅ | ✅ | Existing WebSocketClient consumers (none in genesis, but downstream @suaveplan/network-transport-webrtc signaling client uses the type) keep working — the new transport composes them. |
Acceptance gate
bunx turbo lint typecheck test --filter=@suaveplan/network-transport-websocketsgreen.bunx turbo test:coverage --filter=@suaveplan/network-transport-websocketsat 100/100/100/100 (currently 60 tests pass after the rename; adding the transport+plugin adds another conformance suite).bun run docs:check --filter=@suaveplan/network-transport-websocketsgreen.network-plugin-transport-managerintegration test confirms WS-as-fallback works end-to-end.openspec/specs/network/spec.mdupdated under## MODIFIED Requirementsif any NET-* requirement is tightened.
Out of scope
- Server-side
websocket-gateway(Wave 5, separate package). - TLS / mTLS cert management for production WSS — that's a deployment concern, not a transport concern.
- Custom subprotocols beyond what the existing
WebSocketClientsupports.