ftown-bridge 0.13.5 → 0.15.0
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/dist/centrifugo-client.d.ts +5 -1
- package/dist/centrifugo-client.js +24 -1
- package/dist/centrifugo-client.js.map +1 -1
- package/dist/direct-transport/contract.d.ts +84 -0
- package/dist/direct-transport/contract.js +22 -0
- package/dist/direct-transport/contract.js.map +1 -0
- package/dist/direct-transport/loopback-server.d.ts +76 -0
- package/dist/direct-transport/loopback-server.js +321 -0
- package/dist/direct-transport/loopback-server.js.map +1 -0
- package/dist/direct-transport/peer-manager.d.ts +66 -0
- package/dist/direct-transport/peer-manager.js +364 -0
- package/dist/direct-transport/peer-manager.js.map +1 -0
- package/dist/direct-transport/publish-router.d.ts +48 -0
- package/dist/direct-transport/publish-router.js +72 -0
- package/dist/direct-transport/publish-router.js.map +1 -0
- package/dist/direct-transport/watch-registry.d.ts +38 -0
- package/dist/direct-transport/watch-registry.js +92 -0
- package/dist/direct-transport/watch-registry.js.map +1 -0
- package/dist/index.js +121 -31
- package/dist/index.js.map +1 -1
- package/dist/local-api-server.d.ts +7 -0
- package/dist/local-api-server.js +8 -0
- package/dist/local-api-server.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type SignalMessage } from './contract.js';
|
|
2
|
+
/** Minimal structural view of a node-datachannel DataChannel (injectable for tests). */
|
|
3
|
+
export interface DirectDataChannel {
|
|
4
|
+
sendMessage(msg: string): boolean;
|
|
5
|
+
isOpen(): boolean;
|
|
6
|
+
close(): void;
|
|
7
|
+
onOpen(cb: () => void): void;
|
|
8
|
+
onClosed(cb: () => void): void;
|
|
9
|
+
onError(cb: (err: string) => void): void;
|
|
10
|
+
onMessage(cb: (msg: string | Buffer | ArrayBuffer) => void): void;
|
|
11
|
+
}
|
|
12
|
+
/** Minimal structural view of a node-datachannel PeerConnection (injectable for tests). */
|
|
13
|
+
export interface DirectPeerConnection {
|
|
14
|
+
setRemoteDescription(sdp: string, type: string): void;
|
|
15
|
+
addRemoteCandidate(candidate: string, mid: string): void;
|
|
16
|
+
onLocalDescription(cb: (sdp: string, type: string) => void): void;
|
|
17
|
+
onLocalCandidate(cb: (candidate: string, mid: string) => void): void;
|
|
18
|
+
onStateChange(cb: (state: string) => void): void;
|
|
19
|
+
onDataChannel(cb: (dc: DirectDataChannel) => void): void;
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
export type PeerConnectionFactory = (peerName: string, config: {
|
|
23
|
+
iceServers: string[];
|
|
24
|
+
}) => DirectPeerConnection;
|
|
25
|
+
export interface DirectPeerManagerOptions {
|
|
26
|
+
/** This bridge's identity (as advertised on bridges:presence). */
|
|
27
|
+
bridgeId: string;
|
|
28
|
+
/** Emits bridge-originated answers/ICE over the signaling channel. */
|
|
29
|
+
sendSignal: (msg: SignalMessage) => void;
|
|
30
|
+
/** DataChannel `input` frames feed here (same sink as terminal-input). */
|
|
31
|
+
onInput: (sessionId: string, data: string) => void;
|
|
32
|
+
/** DataChannel `resize` frames feed here (same sink as terminal-input). */
|
|
33
|
+
onResize: (sessionId: string, cols: number, rows: number) => void;
|
|
34
|
+
/** Returns the current full serialized screen for a session on attach. */
|
|
35
|
+
onAttach: (sessionId: string) => string;
|
|
36
|
+
/** node-datachannel PeerConnection factory; defaults to the real module. */
|
|
37
|
+
peerConnectionFactory?: PeerConnectionFactory;
|
|
38
|
+
/** Injectable clock for keepalive/teardown timers (defaults to Date.now). */
|
|
39
|
+
now?: () => number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Manages client-initiated WebRTC peers behind an injectable node-datachannel
|
|
43
|
+
* factory. Answers offers, relays ICE, and fans terminal output/screen to all
|
|
44
|
+
* attached peers over the `ftown` DataChannel.
|
|
45
|
+
*/
|
|
46
|
+
export declare class DirectPeerManager {
|
|
47
|
+
readonly bridgeId: string;
|
|
48
|
+
private readonly sendSignalCb;
|
|
49
|
+
private readonly onInputCb;
|
|
50
|
+
private readonly onResizeCb;
|
|
51
|
+
private readonly onAttachCb;
|
|
52
|
+
private readonly factory;
|
|
53
|
+
private readonly peers;
|
|
54
|
+
constructor(options: DirectPeerManagerOptions);
|
|
55
|
+
/** Never throws — a malformed signal must not propagate into the publication listener. */
|
|
56
|
+
handleSignal(msg: SignalMessage): void;
|
|
57
|
+
sendOutput(sessionId: string, data: string): void;
|
|
58
|
+
sendScreen(sessionId: string, data: string): void;
|
|
59
|
+
hasAttachedPeers(sessionId: string): boolean;
|
|
60
|
+
closeAll(): void;
|
|
61
|
+
teardown(pairId: string): void;
|
|
62
|
+
emitSignal(partial: Omit<SignalMessage, 'bridgeId'>): void;
|
|
63
|
+
getScreen(sessionId: string): string;
|
|
64
|
+
deliverInput(sessionId: string, data: string): void;
|
|
65
|
+
deliverResize(sessionId: string, cols: number, rows: number): void;
|
|
66
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { PeerConnection } from 'node-datachannel';
|
|
2
|
+
import { DIRECT_PROTOCOL_VERSION, PING_INTERVAL_MS, STUN_SERVERS, } from './contract.js';
|
|
3
|
+
const ICE_DISCONNECT_GRACE_MS = 10_000;
|
|
4
|
+
const MAX_MISSED_PINGS = 2;
|
|
5
|
+
/** Peers that never reach channel-open within this window are torn down (leak guard). */
|
|
6
|
+
const PAIRING_TIMEOUT_MS = 30_000;
|
|
7
|
+
/**
|
|
8
|
+
* Max chars per DataChannel frame `data` field. Worst-case UTF-8 is 4 bytes per
|
|
9
|
+
* 2 chars, so 32k chars stays under ~128 KiB — safely below the ~256 KiB max
|
|
10
|
+
* message size negotiated by libdatachannel and browsers. Larger payloads are
|
|
11
|
+
* chunked: for screens, one `screen` frame (stream reset) followed by `output`
|
|
12
|
+
* continuation frames — receivers apply them sequentially, reconstructing the
|
|
13
|
+
* full serialized screen without any wire-format change.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_FRAME_DATA_CHARS = 32_000;
|
|
16
|
+
function chunkData(data) {
|
|
17
|
+
if (data.length <= MAX_FRAME_DATA_CHARS)
|
|
18
|
+
return [data];
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for (let i = 0; i < data.length; i += MAX_FRAME_DATA_CHARS) {
|
|
21
|
+
chunks.push(data.slice(i, i + MAX_FRAME_DATA_CHARS));
|
|
22
|
+
}
|
|
23
|
+
return chunks;
|
|
24
|
+
}
|
|
25
|
+
const defaultPeerConnectionFactory = (peerName, config) =>
|
|
26
|
+
// Verified structural match against node-datachannel's PeerConnection surface.
|
|
27
|
+
new PeerConnection(peerName, config);
|
|
28
|
+
/**
|
|
29
|
+
* One WebRTC peer (one connection attempt, keyed by pairId). Owns its
|
|
30
|
+
* PeerConnection + DataChannel, per-session monotonic seq, attach set, and
|
|
31
|
+
* keepalive/teardown timers.
|
|
32
|
+
*/
|
|
33
|
+
class DirectPeer {
|
|
34
|
+
pairId;
|
|
35
|
+
clientId;
|
|
36
|
+
pc;
|
|
37
|
+
manager;
|
|
38
|
+
dc;
|
|
39
|
+
helloOk = false;
|
|
40
|
+
closed = false;
|
|
41
|
+
attached = new Set();
|
|
42
|
+
seq = new Map();
|
|
43
|
+
pingTimer;
|
|
44
|
+
missedPings = 0;
|
|
45
|
+
iceDisconnectTimer;
|
|
46
|
+
pairingTimer;
|
|
47
|
+
constructor(manager, pc, pairId, clientId) {
|
|
48
|
+
this.manager = manager;
|
|
49
|
+
this.pc = pc;
|
|
50
|
+
this.pairId = pairId;
|
|
51
|
+
this.clientId = clientId;
|
|
52
|
+
pc.onLocalDescription((sdp, type) => {
|
|
53
|
+
if (type === 'answer') {
|
|
54
|
+
this.manager.emitSignal({ type: 'webrtc_answer', pairId, clientId, payload: sdp });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
pc.onLocalCandidate((candidate, mid) => {
|
|
58
|
+
const payload = { candidate, mid };
|
|
59
|
+
this.manager.emitSignal({ type: 'webrtc_ice', pairId, clientId, payload: JSON.stringify(payload) });
|
|
60
|
+
});
|
|
61
|
+
pc.onStateChange((state) => this.onStateChange(state));
|
|
62
|
+
pc.onDataChannel((dc) => this.bindDataChannel(dc));
|
|
63
|
+
// Leak guard: tear down attempts that never reach channel-open (client
|
|
64
|
+
// vanished pre-ICE); keepalive only starts once the channel opens.
|
|
65
|
+
this.pairingTimer = setTimeout(() => this.manager.teardown(this.pairId), PAIRING_TIMEOUT_MS);
|
|
66
|
+
this.pairingTimer.unref?.();
|
|
67
|
+
}
|
|
68
|
+
hasAttached(sessionId) {
|
|
69
|
+
return this.attached.has(sessionId);
|
|
70
|
+
}
|
|
71
|
+
/** Throws on native rejection of the SDP; the manager catches and tears down. */
|
|
72
|
+
applyOffer(sdp) {
|
|
73
|
+
this.pc.setRemoteDescription(sdp, 'offer');
|
|
74
|
+
}
|
|
75
|
+
applyIce(payload) {
|
|
76
|
+
let parsed;
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(payload);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (typeof parsed.candidate !== 'string')
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
this.pc.addRemoteCandidate(parsed.candidate, parsed.mid ?? '');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Malformed candidate ⇒ drop it; ICE can still complete on other candidates.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
sendScreen(sessionId, data) {
|
|
93
|
+
if (!this.attached.has(sessionId))
|
|
94
|
+
return;
|
|
95
|
+
// Large screens are chunked: `screen` resets the stream (seq 0), `output`
|
|
96
|
+
// continuation frames carry the remainder; receivers apply them in order.
|
|
97
|
+
const chunks = chunkData(data);
|
|
98
|
+
this.seq.set(sessionId, chunks.length - 1);
|
|
99
|
+
this.send({ kind: 'screen', sessionId, data: chunks[0], seq: 0 });
|
|
100
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
101
|
+
this.send({ kind: 'output', sessionId, data: chunks[i], seq: i });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
sendOutput(sessionId, data) {
|
|
105
|
+
if (!this.attached.has(sessionId))
|
|
106
|
+
return;
|
|
107
|
+
for (const chunk of chunkData(data)) {
|
|
108
|
+
const next = (this.seq.get(sessionId) ?? 0) + 1;
|
|
109
|
+
this.seq.set(sessionId, next);
|
|
110
|
+
this.send({ kind: 'output', sessionId, data: chunk, seq: next });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
close() {
|
|
114
|
+
if (this.closed)
|
|
115
|
+
return;
|
|
116
|
+
this.closed = true;
|
|
117
|
+
if (this.pingTimer)
|
|
118
|
+
clearInterval(this.pingTimer);
|
|
119
|
+
if (this.iceDisconnectTimer)
|
|
120
|
+
clearTimeout(this.iceDisconnectTimer);
|
|
121
|
+
if (this.pairingTimer)
|
|
122
|
+
clearTimeout(this.pairingTimer);
|
|
123
|
+
this.attached.clear();
|
|
124
|
+
this.seq.clear();
|
|
125
|
+
try {
|
|
126
|
+
this.dc?.close();
|
|
127
|
+
}
|
|
128
|
+
catch { /* already gone */ }
|
|
129
|
+
try {
|
|
130
|
+
this.pc.close();
|
|
131
|
+
}
|
|
132
|
+
catch { /* already gone */ }
|
|
133
|
+
}
|
|
134
|
+
bindDataChannel(dc) {
|
|
135
|
+
this.dc = dc;
|
|
136
|
+
dc.onMessage((msg) => {
|
|
137
|
+
if (typeof msg !== 'string')
|
|
138
|
+
return;
|
|
139
|
+
this.handleFrame(msg);
|
|
140
|
+
});
|
|
141
|
+
dc.onClosed(() => this.manager.teardown(this.pairId));
|
|
142
|
+
dc.onError(() => this.manager.teardown(this.pairId));
|
|
143
|
+
if (dc.isOpen())
|
|
144
|
+
this.startKeepalive();
|
|
145
|
+
else
|
|
146
|
+
dc.onOpen(() => this.startKeepalive());
|
|
147
|
+
}
|
|
148
|
+
startKeepalive() {
|
|
149
|
+
if (this.pairingTimer) {
|
|
150
|
+
clearTimeout(this.pairingTimer);
|
|
151
|
+
this.pairingTimer = undefined;
|
|
152
|
+
}
|
|
153
|
+
if (this.pingTimer)
|
|
154
|
+
return;
|
|
155
|
+
this.missedPings = 0;
|
|
156
|
+
this.pingTimer = setInterval(() => {
|
|
157
|
+
if (this.missedPings >= MAX_MISSED_PINGS) {
|
|
158
|
+
this.manager.teardown(this.pairId);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.missedPings += 1;
|
|
162
|
+
this.send({ kind: 'ping' });
|
|
163
|
+
}, PING_INTERVAL_MS);
|
|
164
|
+
this.pingTimer.unref?.();
|
|
165
|
+
}
|
|
166
|
+
handleFrame(raw) {
|
|
167
|
+
let msg;
|
|
168
|
+
try {
|
|
169
|
+
msg = JSON.parse(raw);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
switch (msg.kind) {
|
|
175
|
+
case 'hello': {
|
|
176
|
+
if (msg.protocolVersion !== DIRECT_PROTOCOL_VERSION) {
|
|
177
|
+
this.manager.teardown(this.pairId);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.helloOk = true;
|
|
181
|
+
this.send({ kind: 'hello_ack', bridgeId: this.manager.bridgeId, protocolVersion: DIRECT_PROTOCOL_VERSION });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
case 'attach': {
|
|
185
|
+
if (!this.helloOk)
|
|
186
|
+
return;
|
|
187
|
+
this.attached.add(msg.sessionId);
|
|
188
|
+
const screen = this.manager.getScreen(msg.sessionId);
|
|
189
|
+
this.sendScreen(msg.sessionId, screen);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
case 'detach': {
|
|
193
|
+
this.attached.delete(msg.sessionId);
|
|
194
|
+
this.seq.delete(msg.sessionId);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
case 'input': {
|
|
198
|
+
if (!this.helloOk)
|
|
199
|
+
return;
|
|
200
|
+
this.manager.deliverInput(msg.sessionId, msg.data);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
case 'resize': {
|
|
204
|
+
if (!this.helloOk)
|
|
205
|
+
return;
|
|
206
|
+
this.manager.deliverResize(msg.sessionId, msg.cols, msg.rows);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
case 'ping': {
|
|
210
|
+
this.send({ kind: 'pong' });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
case 'pong': {
|
|
214
|
+
this.missedPings = 0;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
default:
|
|
218
|
+
// Unknown kind ⇒ ignore.
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
onStateChange(state) {
|
|
223
|
+
if (state === 'disconnected') {
|
|
224
|
+
if (this.iceDisconnectTimer)
|
|
225
|
+
return;
|
|
226
|
+
this.iceDisconnectTimer = setTimeout(() => this.manager.teardown(this.pairId), ICE_DISCONNECT_GRACE_MS);
|
|
227
|
+
this.iceDisconnectTimer.unref?.();
|
|
228
|
+
}
|
|
229
|
+
else if (state === 'connected') {
|
|
230
|
+
if (this.iceDisconnectTimer) {
|
|
231
|
+
clearTimeout(this.iceDisconnectTimer);
|
|
232
|
+
this.iceDisconnectTimer = undefined;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (state === 'failed' || state === 'closed') {
|
|
236
|
+
this.manager.teardown(this.pairId);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
send(msg) {
|
|
240
|
+
if (this.closed || !this.dc || !this.dc.isOpen())
|
|
241
|
+
return;
|
|
242
|
+
try {
|
|
243
|
+
this.dc.sendMessage(JSON.stringify(msg));
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Drop the frame rather than kill the connection; a genuinely dead
|
|
247
|
+
// channel is detected by onClosed/onError or missed pings.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Manages client-initiated WebRTC peers behind an injectable node-datachannel
|
|
253
|
+
* factory. Answers offers, relays ICE, and fans terminal output/screen to all
|
|
254
|
+
* attached peers over the `ftown` DataChannel.
|
|
255
|
+
*/
|
|
256
|
+
export class DirectPeerManager {
|
|
257
|
+
bridgeId;
|
|
258
|
+
sendSignalCb;
|
|
259
|
+
onInputCb;
|
|
260
|
+
onResizeCb;
|
|
261
|
+
onAttachCb;
|
|
262
|
+
factory;
|
|
263
|
+
peers = new Map();
|
|
264
|
+
constructor(options) {
|
|
265
|
+
this.bridgeId = options.bridgeId;
|
|
266
|
+
this.sendSignalCb = options.sendSignal;
|
|
267
|
+
this.onInputCb = options.onInput;
|
|
268
|
+
this.onResizeCb = options.onResize;
|
|
269
|
+
this.onAttachCb = options.onAttach;
|
|
270
|
+
this.factory = options.peerConnectionFactory ?? defaultPeerConnectionFactory;
|
|
271
|
+
}
|
|
272
|
+
/** Never throws — a malformed signal must not propagate into the publication listener. */
|
|
273
|
+
handleSignal(msg) {
|
|
274
|
+
if (msg.bridgeId !== this.bridgeId)
|
|
275
|
+
return;
|
|
276
|
+
if (typeof msg.pairId !== 'string' || msg.pairId === '')
|
|
277
|
+
return;
|
|
278
|
+
try {
|
|
279
|
+
switch (msg.type) {
|
|
280
|
+
case 'webrtc_offer': {
|
|
281
|
+
if (typeof msg.payload !== 'string' || msg.payload === '')
|
|
282
|
+
return;
|
|
283
|
+
if (typeof msg.clientId !== 'string' || msg.clientId === '')
|
|
284
|
+
return;
|
|
285
|
+
// Replace any prior pairing for this pairId AND any stale peer of the
|
|
286
|
+
// same client (reconnect) so a session never gets duplicate frames.
|
|
287
|
+
this.teardown(msg.pairId);
|
|
288
|
+
for (const peer of [...this.peers.values()]) {
|
|
289
|
+
if (peer.clientId === msg.clientId)
|
|
290
|
+
this.teardown(peer.pairId);
|
|
291
|
+
}
|
|
292
|
+
const pc = this.factory(`ftown-${msg.pairId}`, { iceServers: [...STUN_SERVERS] });
|
|
293
|
+
const peer = new DirectPeer(this, pc, msg.pairId, msg.clientId);
|
|
294
|
+
this.peers.set(msg.pairId, peer);
|
|
295
|
+
try {
|
|
296
|
+
peer.applyOffer(msg.payload);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Malformed SDP: abort just this pairing attempt and tell the client.
|
|
300
|
+
this.teardown(msg.pairId);
|
|
301
|
+
this.emitSignal({ type: 'webrtc_close', pairId: msg.pairId, clientId: msg.clientId, payload: '' });
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
case 'webrtc_ice': {
|
|
306
|
+
if (typeof msg.payload !== 'string')
|
|
307
|
+
return;
|
|
308
|
+
this.peers.get(msg.pairId)?.applyIce(msg.payload);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
case 'webrtc_close': {
|
|
312
|
+
this.teardown(msg.pairId);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
default:
|
|
316
|
+
// webrtc_answer (our own echo) and anything else ⇒ ignore.
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
console.error('[DirectTransport] Failed to handle signal:', err);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
sendOutput(sessionId, data) {
|
|
325
|
+
for (const peer of this.peers.values())
|
|
326
|
+
peer.sendOutput(sessionId, data);
|
|
327
|
+
}
|
|
328
|
+
sendScreen(sessionId, data) {
|
|
329
|
+
for (const peer of this.peers.values())
|
|
330
|
+
peer.sendScreen(sessionId, data);
|
|
331
|
+
}
|
|
332
|
+
hasAttachedPeers(sessionId) {
|
|
333
|
+
for (const peer of this.peers.values()) {
|
|
334
|
+
if (peer.hasAttached(sessionId))
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
closeAll() {
|
|
340
|
+
for (const peer of this.peers.values())
|
|
341
|
+
peer.close();
|
|
342
|
+
this.peers.clear();
|
|
343
|
+
}
|
|
344
|
+
teardown(pairId) {
|
|
345
|
+
const peer = this.peers.get(pairId);
|
|
346
|
+
if (!peer)
|
|
347
|
+
return;
|
|
348
|
+
this.peers.delete(pairId);
|
|
349
|
+
peer.close();
|
|
350
|
+
}
|
|
351
|
+
emitSignal(partial) {
|
|
352
|
+
this.sendSignalCb({ ...partial, bridgeId: this.bridgeId });
|
|
353
|
+
}
|
|
354
|
+
getScreen(sessionId) {
|
|
355
|
+
return this.onAttachCb(sessionId);
|
|
356
|
+
}
|
|
357
|
+
deliverInput(sessionId, data) {
|
|
358
|
+
this.onInputCb(sessionId, data);
|
|
359
|
+
}
|
|
360
|
+
deliverResize(sessionId, cols, rows) {
|
|
361
|
+
this.onResizeCb(sessionId, cols, rows);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=peer-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-manager.js","sourceRoot":"","sources":["../../src/direct-transport/peer-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type DirectCommandMessage } from './contract.js';
|
|
2
|
+
import type { DirectPeerManager } from './peer-manager.js';
|
|
3
|
+
import type { WatchRegistry } from './watch-registry.js';
|
|
4
|
+
/** Minimal view of CentrifugoClient's terminal publish surface (R2 fallback path). */
|
|
5
|
+
export interface CentrifugoPublisher {
|
|
6
|
+
publishTerminalData(userId: string, sessionId: string, data: string): Promise<void>;
|
|
7
|
+
publishTerminalScreen(userId: string, sessionId: string, raw: string): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/** Minimal view of the loopback WS rung (LoopbackPeerServer) the router fans to. */
|
|
10
|
+
export interface LoopbackPeerServerLike {
|
|
11
|
+
sendOutput(sessionId: string, data: string): void;
|
|
12
|
+
sendScreen(sessionId: string, data: string): void;
|
|
13
|
+
hasAttachedPeers(sessionId: string): boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface PublishRouterOptions {
|
|
16
|
+
registry: WatchRegistry;
|
|
17
|
+
peerManager: DirectPeerManager;
|
|
18
|
+
centrifugo: CentrifugoPublisher;
|
|
19
|
+
userId: string;
|
|
20
|
+
/** Optional loopback WS rung; output/screen fan out to it as well as WebRTC. */
|
|
21
|
+
loopback?: LoopbackPeerServerLike;
|
|
22
|
+
/**
|
|
23
|
+
* Optional guard: only register watchers for sessions this bridge owns (watch
|
|
24
|
+
* messages fan out to every bridge on the shared commands channel). Defaults
|
|
25
|
+
* to accepting all sessionIds.
|
|
26
|
+
*/
|
|
27
|
+
isKnownSession?: (sessionId: string) => boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Implements R2: terminal output/screen always fan out to direct-attached peers;
|
|
31
|
+
* they also go to Centrifugo iff the session has an unexpired remote watcher.
|
|
32
|
+
* Truncation for the Centrifugo path stays inside CentrifugoClient's publishers.
|
|
33
|
+
*/
|
|
34
|
+
export declare class PublishRouter {
|
|
35
|
+
private readonly registry;
|
|
36
|
+
private readonly peerManager;
|
|
37
|
+
private readonly centrifugo;
|
|
38
|
+
private readonly userId;
|
|
39
|
+
private readonly loopback?;
|
|
40
|
+
private readonly isKnownSession;
|
|
41
|
+
constructor(options: PublishRouterOptions);
|
|
42
|
+
/** R2 gating: a session is direct-attached if EITHER local rung has a peer. */
|
|
43
|
+
hasAttachedPeers(sessionId: string): boolean;
|
|
44
|
+
publishTerminalData(sessionId: string, data: string): void;
|
|
45
|
+
publishTerminalScreen(sessionId: string, screen: string): void;
|
|
46
|
+
/** Never throws — runs inside the Centrifugo publication listener. */
|
|
47
|
+
handleCommand(msg: DirectCommandMessage): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isSignalMessage, isWatchMessage } from './contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* Implements R2: terminal output/screen always fan out to direct-attached peers;
|
|
4
|
+
* they also go to Centrifugo iff the session has an unexpired remote watcher.
|
|
5
|
+
* Truncation for the Centrifugo path stays inside CentrifugoClient's publishers.
|
|
6
|
+
*/
|
|
7
|
+
export class PublishRouter {
|
|
8
|
+
registry;
|
|
9
|
+
peerManager;
|
|
10
|
+
centrifugo;
|
|
11
|
+
userId;
|
|
12
|
+
loopback;
|
|
13
|
+
isKnownSession;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.registry = options.registry;
|
|
16
|
+
this.peerManager = options.peerManager;
|
|
17
|
+
this.centrifugo = options.centrifugo;
|
|
18
|
+
this.userId = options.userId;
|
|
19
|
+
this.loopback = options.loopback;
|
|
20
|
+
this.isKnownSession = options.isKnownSession ?? (() => true);
|
|
21
|
+
}
|
|
22
|
+
/** R2 gating: a session is direct-attached if EITHER local rung has a peer. */
|
|
23
|
+
hasAttachedPeers(sessionId) {
|
|
24
|
+
return (this.peerManager.hasAttachedPeers(sessionId) ||
|
|
25
|
+
(this.loopback?.hasAttachedPeers(sessionId) ?? false));
|
|
26
|
+
}
|
|
27
|
+
publishTerminalData(sessionId, data) {
|
|
28
|
+
this.peerManager.sendOutput(sessionId, data);
|
|
29
|
+
this.loopback?.sendOutput(sessionId, data);
|
|
30
|
+
if (this.registry.hasWatchers(sessionId)) {
|
|
31
|
+
this.centrifugo.publishTerminalData(this.userId, sessionId, data).catch((err) => {
|
|
32
|
+
console.error(`[DirectTransport] Failed to publish terminal data for ${sessionId}:`, err);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
publishTerminalScreen(sessionId, screen) {
|
|
37
|
+
this.peerManager.sendScreen(sessionId, screen);
|
|
38
|
+
this.loopback?.sendScreen(sessionId, screen);
|
|
39
|
+
if (this.registry.hasWatchers(sessionId)) {
|
|
40
|
+
this.centrifugo.publishTerminalScreen(this.userId, sessionId, screen).catch((err) => {
|
|
41
|
+
console.error(`[DirectTransport] Failed to publish terminal screen for ${sessionId}:`, err);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Never throws — runs inside the Centrifugo publication listener. */
|
|
46
|
+
handleCommand(msg) {
|
|
47
|
+
try {
|
|
48
|
+
if (isSignalMessage(msg)) {
|
|
49
|
+
this.peerManager.handleSignal(msg);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (isWatchMessage(msg)) {
|
|
53
|
+
if (typeof msg.sessionId !== 'string' || msg.sessionId === '')
|
|
54
|
+
return;
|
|
55
|
+
if (typeof msg.clientId !== 'string' || msg.clientId === '')
|
|
56
|
+
return;
|
|
57
|
+
if (msg.type === 'terminal_watch') {
|
|
58
|
+
if (!this.isKnownSession(msg.sessionId))
|
|
59
|
+
return;
|
|
60
|
+
this.registry.watch(msg.sessionId, msg.clientId);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.registry.unwatch(msg.sessionId, msg.clientId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error('[DirectTransport] Failed to handle direct command:', err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=publish-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-router.js","sourceRoot":"","sources":["../../src/direct-transport/publish-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type FirstWatcherCallback = (sessionId: string) => void;
|
|
2
|
+
export interface WatchRegistryOptions {
|
|
3
|
+
/** Injectable clock (defaults to Date.now) so tests can control expiry. */
|
|
4
|
+
now?: () => number;
|
|
5
|
+
/** Watcher lifetime; defaults to the frozen WATCH_TTL_MS. */
|
|
6
|
+
ttlMs?: number;
|
|
7
|
+
/** Housekeeping sweep interval; set 0 to disable the timer (lazy expiry only). */
|
|
8
|
+
sweepIntervalMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Tracks unexpired remote watchers per session. Expiry is lazy (evaluated
|
|
12
|
+
* against the injectable clock on every read) with an optional unref'd sweep
|
|
13
|
+
* timer for memory housekeeping. `onFirstWatcher` fires when a session
|
|
14
|
+
* transitions 0 -> 1 live watchers; `onNewWatcher` fires whenever a clientId
|
|
15
|
+
* that was not already a live watcher registers (first watcher, each
|
|
16
|
+
* additional distinct client, and re-registration after TTL expiry — but not
|
|
17
|
+
* heartbeat refreshes).
|
|
18
|
+
*/
|
|
19
|
+
export declare class WatchRegistry {
|
|
20
|
+
private readonly watchers;
|
|
21
|
+
private readonly firstWatcherCallbacks;
|
|
22
|
+
private readonly newWatcherCallbacks;
|
|
23
|
+
private readonly now;
|
|
24
|
+
private readonly ttlMs;
|
|
25
|
+
private readonly sweepTimer?;
|
|
26
|
+
constructor(options?: WatchRegistryOptions);
|
|
27
|
+
watch(sessionId: string, clientId: string): void;
|
|
28
|
+
unwatch(sessionId: string, clientId: string): void;
|
|
29
|
+
hasWatchers(sessionId: string): boolean;
|
|
30
|
+
onFirstWatcher(cb: FirstWatcherCallback): void;
|
|
31
|
+
/** Fires for every distinct client registration (incl. post-expiry re-registration). */
|
|
32
|
+
onNewWatcher(cb: FirstWatcherCallback): void;
|
|
33
|
+
dispose(): void;
|
|
34
|
+
/** Prunes expired entries from `inner` and returns the remaining live count. */
|
|
35
|
+
private liveCount;
|
|
36
|
+
private sweep;
|
|
37
|
+
}
|
|
38
|
+
export {};
|