@robota-sdk/agent-transport-webrtc 3.0.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +694 -0
- package/LICENSE +661 -0
- package/README.md +111 -0
- package/dist/node/index.cjs +1 -0
- package/dist/node/index.d.cts +1650 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1650 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +93 -0
- package/src/__tests__/admission-secure-by-default.test.ts +63 -0
- package/src/__tests__/admission-steps.test.ts +46 -0
- package/src/__tests__/device-mesh-relay.test.ts +441 -0
- package/src/__tests__/device-mesh.test.ts +682 -0
- package/src/__tests__/discovering-mesh-relay.test.ts +234 -0
- package/src/__tests__/enrollment-link.test.ts +103 -0
- package/src/__tests__/fake-datachannel.ts +83 -0
- package/src/__tests__/handoff-grant-gate.test.ts +403 -0
- package/src/__tests__/local-peer-proof-gate.test.ts +215 -0
- package/src/__tests__/mesh-admission-contract.test.ts +38 -0
- package/src/__tests__/mesh-discovery.test.ts +820 -0
- package/src/__tests__/mesh-fixtures.ts +148 -0
- package/src/__tests__/mesh-internet.test.ts +942 -0
- package/src/__tests__/mesh-turn-relay.test.ts +161 -0
- package/src/__tests__/negotiated-certificate.test.ts +76 -0
- package/src/__tests__/nostr-source-condition.test.ts +27 -0
- package/src/__tests__/operator-approval-gate.test.ts +308 -0
- package/src/__tests__/outbound-delivery-carrier.test.ts +155 -0
- package/src/__tests__/pairing-e2e.test.ts +237 -0
- package/src/__tests__/pairing-gate-e3.test.ts +194 -0
- package/src/__tests__/pairing-gate-e4.test.ts +110 -0
- package/src/__tests__/pairing-gate.test.ts +296 -0
- package/src/__tests__/pairing-relay-in-the-middle.test.ts +225 -0
- package/src/__tests__/rtc-channel-close.test.ts +52 -0
- package/src/__tests__/rtc-peer-candidates.test.ts +61 -0
- package/src/__tests__/session-attachment-app-view.test.ts +66 -0
- package/src/__tests__/session-attachment-budget.test.ts +136 -0
- package/src/__tests__/turn-server-bind.test.ts +59 -0
- package/src/__tests__/turn-server.test.ts +335 -0
- package/src/__tests__/turn-test-client.ts +256 -0
- package/src/__tests__/webrtc-implementation.test.ts +83 -0
- package/src/__tests__/webrtc-transport.test.ts +349 -0
- package/src/__tests__/ws-mesh-relay-client.test.ts +95 -0
- package/src/__tests__/ws-signaling-client.test.ts +108 -0
- package/src/admission-steps.ts +55 -0
- package/src/channel-delivery.ts +44 -0
- package/src/datachannel-loader.ts +86 -0
- package/src/device-mesh-node.ts +1019 -0
- package/src/discovering-mesh-relay.ts +675 -0
- package/src/enrollment-link.ts +465 -0
- package/src/handoff-grant-gate.ts +186 -0
- package/src/index.ts +139 -0
- package/src/local-peer-proof.ts +135 -0
- package/src/mainline-dht-store.ts +174 -0
- package/src/mesh-dht.ts +446 -0
- package/src/mesh-discovery.ts +87 -0
- package/src/mesh-item-store.ts +287 -0
- package/src/mesh-lan-listener.ts +193 -0
- package/src/mesh-mdns.ts +406 -0
- package/src/mesh-peer-link.ts +600 -0
- package/src/mesh-public-relays.ts +25 -0
- package/src/mesh-records.ts +357 -0
- package/src/mesh-relay.ts +98 -0
- package/src/mesh-signal.ts +116 -0
- package/src/mesh-turn-relay.ts +238 -0
- package/src/negotiated-certificate.ts +72 -0
- package/src/nostr-mesh-relay.ts +391 -0
- package/src/pairing-channel-lifecycle.ts +31 -0
- package/src/pairing-controllers.ts +81 -0
- package/src/pairing-frames.ts +34 -0
- package/src/pairing-gate-options.ts +133 -0
- package/src/pairing-gate.ts +397 -0
- package/src/rtc-peer.ts +429 -0
- package/src/session-attachment.ts +96 -0
- package/src/signaling.ts +62 -0
- package/src/stun-message.ts +341 -0
- package/src/transport-lifecycle-error.ts +11 -0
- package/src/turn-server.ts +1028 -0
- package/src/webrtc-delivery-lifecycle.ts +54 -0
- package/src/webrtc-transport-options.ts +87 -0
- package/src/webrtc-transport.ts +368 -0
- package/src/ws-mesh-relay-client.ts +117 -0
- package/src/ws-signaling-client.ts +139 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production `ISignalingClient` over a WebSocket connection to the `@robota-sdk/remote-signaling` relay
|
|
3
|
+
* (REMOTE-004 Stage B2). This is the **Node host-side** client (the browser remote client, Stage D, implements
|
|
4
|
+
* `ISignalingClient` on the native `WebSocket`).
|
|
5
|
+
*
|
|
6
|
+
* On open it `join`s the rendezvous and flushes any signals produced before the socket opened (the offer is
|
|
7
|
+
* created in `WebRtcTransport.start()`, which can run before the socket connects — buffering avoids dropping it).
|
|
8
|
+
* Relay `error` frames, socket errors, and a close-before-join are surfaced through an explicit `onError`
|
|
9
|
+
* callback — **never a silent degrade** (no-fallback, mirroring `loadReplayProvider`/`loadDataChannel`). The relay is
|
|
10
|
+
* content-blind: this client only ever emits `join` + `signal` frames and only ever consumes `joined` / `signal`
|
|
11
|
+
* / `error` frames.
|
|
12
|
+
*/
|
|
13
|
+
import WebSocket from 'ws';
|
|
14
|
+
|
|
15
|
+
import type { ISignalingClient, ISignalMessage, TSignalKind } from './signaling.js';
|
|
16
|
+
|
|
17
|
+
/** A minimal WebSocket-like surface — so tests can inject a fake without a real socket. */
|
|
18
|
+
export interface IWebSocketLike {
|
|
19
|
+
send(data: string): void;
|
|
20
|
+
close(): void;
|
|
21
|
+
readyState: number;
|
|
22
|
+
on(event: 'open' | 'message' | 'error' | 'close', handler: (arg: unknown) => void): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IWsSignalingClientOptions {
|
|
26
|
+
/** Relay URL, e.g. `ws://127.0.0.1:1234`. */
|
|
27
|
+
readonly url: string;
|
|
28
|
+
/** Rendezvous id to join. */
|
|
29
|
+
readonly rendezvous: string;
|
|
30
|
+
/**
|
|
31
|
+
* Explicit error sink. Called on a relay `error` frame, a socket error, or a close-before-join — the signaling
|
|
32
|
+
* channel cannot function and the caller must know (no silent degrade).
|
|
33
|
+
*/
|
|
34
|
+
readonly onError?: (error: Error) => void;
|
|
35
|
+
/** Called once the relay confirms the rendezvous `join` (the `joined` frame). */
|
|
36
|
+
readonly onReady?: () => void;
|
|
37
|
+
/** Injectable socket factory (defaults to a real `ws` WebSocket) — for tests. */
|
|
38
|
+
readonly createSocket?: (url: string) => IWebSocketLike;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const SIGNAL_KINDS: ReadonlySet<string> = new Set<TSignalKind>(['offer', 'answer', 'ice']);
|
|
42
|
+
const WS_OPEN = 1;
|
|
43
|
+
|
|
44
|
+
function defaultCreateSocket(url: string): IWebSocketLike {
|
|
45
|
+
return new WebSocket(url) as unknown as IWebSocketLike;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class WsSignalingClient implements ISignalingClient {
|
|
49
|
+
private readonly socket: IWebSocketLike;
|
|
50
|
+
private readonly handlers: ((message: ISignalMessage) => void)[] = [];
|
|
51
|
+
private readonly outbox: ISignalMessage[] = [];
|
|
52
|
+
private joined = false;
|
|
53
|
+
private closed = false;
|
|
54
|
+
|
|
55
|
+
public constructor(private readonly options: IWsSignalingClientOptions) {
|
|
56
|
+
const factory = options.createSocket ?? defaultCreateSocket;
|
|
57
|
+
this.socket = factory(options.url);
|
|
58
|
+
this.socket.on('open', () => this.handleOpen());
|
|
59
|
+
this.socket.on('message', (data) => this.handleMessage(data));
|
|
60
|
+
this.socket.on('error', (err) =>
|
|
61
|
+
this.fail(err instanceof Error ? err : new Error(String(err))),
|
|
62
|
+
);
|
|
63
|
+
this.socket.on('close', () => this.handleClose());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private handleOpen(): void {
|
|
67
|
+
this.socket.send(JSON.stringify({ type: 'join', rendezvous: this.options.rendezvous }));
|
|
68
|
+
// Flush any signals produced before the socket opened (e.g. the offer from WebRtcTransport.start()).
|
|
69
|
+
for (const message of this.outbox) this.writeSignal(message);
|
|
70
|
+
this.outbox.length = 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private handleMessage(data: unknown): void {
|
|
74
|
+
let frame: unknown;
|
|
75
|
+
try {
|
|
76
|
+
frame = JSON.parse(typeof data === 'string' ? data : String(data));
|
|
77
|
+
} catch {
|
|
78
|
+
return; // a non-JSON frame from the relay is ignored (the relay only emits JSON)
|
|
79
|
+
}
|
|
80
|
+
if (typeof frame !== 'object' || frame === null) return;
|
|
81
|
+
const record = frame as Record<string, unknown>;
|
|
82
|
+
if (record.type === 'joined') {
|
|
83
|
+
this.joined = true;
|
|
84
|
+
this.options.onReady?.();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (record.type === 'error') {
|
|
88
|
+
this.fail(new Error(`signaling relay rejected the connection: ${String(record.reason)}`));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (
|
|
92
|
+
record.type === 'signal' &&
|
|
93
|
+
typeof record.kind === 'string' &&
|
|
94
|
+
SIGNAL_KINDS.has(record.kind)
|
|
95
|
+
) {
|
|
96
|
+
const message: ISignalMessage = { kind: record.kind as TSignalKind, data: record.data };
|
|
97
|
+
for (const handler of this.handlers) handler(message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private handleClose(): void {
|
|
102
|
+
if (this.closed) return; // an intentional close() is not an error
|
|
103
|
+
if (!this.joined) {
|
|
104
|
+
this.fail(new Error('signaling socket closed before the rendezvous was joined'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fail(error: Error): void {
|
|
109
|
+
this.options.onError?.(error);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private writeSignal(message: ISignalMessage): void {
|
|
113
|
+
this.socket.send(JSON.stringify({ type: 'signal', kind: message.kind, data: message.data }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public send(message: ISignalMessage): void {
|
|
117
|
+
if (this.closed) return;
|
|
118
|
+
if (this.socket.readyState === WS_OPEN) {
|
|
119
|
+
this.writeSignal(message);
|
|
120
|
+
} else {
|
|
121
|
+
this.outbox.push(message); // buffer until open (see handleOpen)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public onSignal(handler: (message: ISignalMessage) => void): () => void {
|
|
126
|
+
this.handlers.push(handler);
|
|
127
|
+
return () => {
|
|
128
|
+
const index = this.handlers.indexOf(handler);
|
|
129
|
+
if (index >= 0) this.handlers.splice(index, 1);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public close(): void {
|
|
134
|
+
this.closed = true;
|
|
135
|
+
this.handlers.length = 0;
|
|
136
|
+
this.outbox.length = 0;
|
|
137
|
+
this.socket.close();
|
|
138
|
+
}
|
|
139
|
+
}
|