@livedesk/hub 0.1.26 → 0.1.28
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/package.json +2 -2
- package/src/agents/agent-manager.js +27 -11
- package/src/agents/codex-agent-runtime.js +325 -41
- package/src/frame-packet-contract.mjs +427 -0
- package/src/live-capture-transition-retry.mjs +297 -0
- package/src/mode4-atlas-pool.js +17 -3
- package/src/mode4-atlas-worker.js +134 -21
- package/src/mode4-atlas.js +180 -16
- package/src/remote-audio-subscription-contract.mjs +109 -0
- package/src/remote-audio-subscription-contract.test.mjs +72 -0
- package/src/remote-hub.js +6374 -3890
- package/src/server.js +3234 -2202
- package/src/transport/agent-binary-ingress.js +810 -0
- package/src/transport/relay-hub-control.js +372 -56
- package/src/transport/udp-hub-transport.js +215 -51
- package/src/transport/udp-protocol.js +302 -70
|
@@ -3,15 +3,19 @@ import { lookup } from 'node:dns/promises';
|
|
|
3
3
|
import dgram from 'node:dgram';
|
|
4
4
|
import {
|
|
5
5
|
UDP_MAX_DATAGRAM_BYTES,
|
|
6
|
+
UDP_FRAME_CHUNK_PROTOCOL,
|
|
6
7
|
UDP_P2P_PROTOCOL,
|
|
7
8
|
UdpFrameReassembler,
|
|
8
9
|
UdpReplayWindow,
|
|
9
|
-
decodeRendezvousMessage,
|
|
10
|
-
decodeUdpPacket,
|
|
10
|
+
decodeRendezvousMessage,
|
|
11
|
+
decodeUdpPacket,
|
|
11
12
|
encodeRendezvousMessage,
|
|
12
13
|
encodeUdpPacket,
|
|
13
|
-
createUdpSessionKey
|
|
14
|
-
|
|
14
|
+
createUdpSessionKey,
|
|
15
|
+
isUdpBinaryDatagram,
|
|
16
|
+
peekUdpSessionRouteKey,
|
|
17
|
+
udpSessionRouteKey
|
|
18
|
+
} from './udp-protocol.js';
|
|
15
19
|
|
|
16
20
|
function isEnabled(value, fallback = false) {
|
|
17
21
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -69,9 +73,21 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
69
73
|
const punchTimeoutMs = numberEnv(env.LIVEDESK_UDP_PUNCH_TIMEOUT_MS, 500, 10_000, 3000);
|
|
70
74
|
const frameTimeoutMs = numberEnv(env.LIVEDESK_UDP_FRAME_TIMEOUT_MS, 20, 2000, 500);
|
|
71
75
|
const maxPendingFrames = numberEnv(env.LIVEDESK_UDP_MAX_PENDING_FRAMES, 1, 16, 2);
|
|
72
|
-
const trace = isEnabled(env.LIVEDESK_UDP_TRACE, false);
|
|
73
|
-
const socket = dgram.createSocket('udp4');
|
|
74
|
-
const sessions = new Map();
|
|
76
|
+
const trace = isEnabled(env.LIVEDESK_UDP_TRACE, false);
|
|
77
|
+
const socket = dgram.createSocket('udp4');
|
|
78
|
+
const sessions = new Map();
|
|
79
|
+
const sessionsBySessionId = new Map();
|
|
80
|
+
const sessionsByRouteKey = new Map();
|
|
81
|
+
const resource = {
|
|
82
|
+
datagramsReceived: 0,
|
|
83
|
+
binaryDatagramsReceived: 0,
|
|
84
|
+
rendezvousJsonParseAttempts: 0,
|
|
85
|
+
binaryJsonParseAttempts: 0,
|
|
86
|
+
sessionLookupCount: 0,
|
|
87
|
+
linearSessionScanCount: 0,
|
|
88
|
+
authenticatedDatagrams: 0,
|
|
89
|
+
rejectedDatagrams: 0
|
|
90
|
+
};
|
|
75
91
|
let frameHandler = () => {};
|
|
76
92
|
let eventHandler = () => {};
|
|
77
93
|
let started = false;
|
|
@@ -80,6 +96,7 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
80
96
|
let rendezvousAddresses = new Set();
|
|
81
97
|
let rendezvousRefreshTimer = null;
|
|
82
98
|
let rendezvousRefreshPromise = null;
|
|
99
|
+
let frameExpiryTimer = null;
|
|
83
100
|
|
|
84
101
|
function emit(type, event = {}) {
|
|
85
102
|
eventHandler(type, event);
|
|
@@ -172,17 +189,31 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
172
189
|
rendezvousRefreshTimer = null;
|
|
173
190
|
}
|
|
174
191
|
|
|
192
|
+
function startFrameExpiryTimer() {
|
|
193
|
+
if (frameExpiryTimer) return;
|
|
194
|
+
frameExpiryTimer = setInterval(() => {
|
|
195
|
+
const now = Date.now();
|
|
196
|
+
for (const session of sessions.values()) {
|
|
197
|
+
session.reassembler.expire(now);
|
|
198
|
+
}
|
|
199
|
+
}, Math.max(20, Math.min(250, frameTimeoutMs)));
|
|
200
|
+
frameExpiryTimer.unref?.();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function stopFrameExpiryTimer() {
|
|
204
|
+
if (frameExpiryTimer) clearInterval(frameExpiryTimer);
|
|
205
|
+
frameExpiryTimer = null;
|
|
206
|
+
}
|
|
207
|
+
|
|
175
208
|
function isTrustedRendezvousSource(rinfo) {
|
|
176
209
|
return Number(rinfo?.port) === rendezvousPort
|
|
177
210
|
&& rendezvousAddresses.has(normalizedAddress(rinfo?.address));
|
|
178
211
|
}
|
|
179
212
|
|
|
180
213
|
function sessionForPacket(packet) {
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const sessionId = packet.subarray(27, 27 + sessionLength).toString('utf8');
|
|
185
|
-
return [...sessions.values()].find(item => item.sessionId === sessionId) || null;
|
|
214
|
+
resource.sessionLookupCount += 1;
|
|
215
|
+
const routeKey = peekUdpSessionRouteKey(packet);
|
|
216
|
+
return routeKey === null ? null : sessionsByRouteKey.get(routeKey) || null;
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
function bindAuthenticatedClientEndpoint(session, rinfo) {
|
|
@@ -200,7 +231,7 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
200
231
|
}
|
|
201
232
|
|
|
202
233
|
function onRendezvousPeer(message, rinfo) {
|
|
203
|
-
const session =
|
|
234
|
+
const session = sessionsBySessionId.get(safeText(message.roomId, 160));
|
|
204
235
|
if (!session || message.protocol !== UDP_P2P_PROTOCOL || !isTrustedRendezvousSource(rinfo)) return;
|
|
205
236
|
const address = safeText(message.host, 128);
|
|
206
237
|
const port = Number(message.port);
|
|
@@ -223,16 +254,43 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
223
254
|
emit('UdpPeerEndpointObserved', { deviceId: session.deviceId, endpoint: `${address}:${port}` });
|
|
224
255
|
}
|
|
225
256
|
|
|
226
|
-
socket.on('message', (payload, rinfo) => {
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
|
|
257
|
+
socket.on('message', (payload, rinfo) => {
|
|
258
|
+
resource.datagramsReceived += 1;
|
|
259
|
+
if (!isUdpBinaryDatagram(payload)) {
|
|
260
|
+
// Rendezvous messages are the only JSON datagrams. Binary packets
|
|
261
|
+
// never pay a speculative JSON parse/exception before routing.
|
|
262
|
+
if (payload?.[0] !== 0x7b) {
|
|
263
|
+
resource.rejectedDatagrams += 1;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
resource.rendezvousJsonParseAttempts += 1;
|
|
267
|
+
const rendezvous = decodeRendezvousMessage(payload);
|
|
268
|
+
if (rendezvous?.type === 'rendezvous.peer') {
|
|
269
|
+
onRendezvousPeer(rendezvous, rinfo);
|
|
270
|
+
} else {
|
|
271
|
+
resource.rejectedDatagrams += 1;
|
|
272
|
+
}
|
|
230
273
|
return;
|
|
231
274
|
}
|
|
275
|
+
resource.binaryDatagramsReceived += 1;
|
|
232
276
|
const session = sessionForPacket(payload);
|
|
233
|
-
if (!session)
|
|
234
|
-
|
|
235
|
-
|
|
277
|
+
if (!session) {
|
|
278
|
+
resource.rejectedDatagrams += 1;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
session.resource.binaryDatagramsReceived += 1;
|
|
282
|
+
session.resource.decryptAttempts += 1;
|
|
283
|
+
const packet = decodeUdpPacket(payload, session.key, {
|
|
284
|
+
id: session.sessionId,
|
|
285
|
+
bytes: session.sessionIdBytes
|
|
286
|
+
});
|
|
287
|
+
if (!packet || packet.sessionId !== session.sessionId || !session.replay.accept(packet.sequence)) {
|
|
288
|
+
session.resource.rejectedDatagrams += 1;
|
|
289
|
+
resource.rejectedDatagrams += 1;
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
resource.authenticatedDatagrams += 1;
|
|
293
|
+
session.resource.authenticatedDatagrams += 1;
|
|
236
294
|
bindAuthenticatedClientEndpoint(session, rinfo);
|
|
237
295
|
session.lastPacketAt = Date.now();
|
|
238
296
|
if (packet.type === 'probe') {
|
|
@@ -263,7 +321,7 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
263
321
|
if (!complete) return;
|
|
264
322
|
session.framesReceived += 1;
|
|
265
323
|
const reassembly = session.reassembler.getStats();
|
|
266
|
-
frameHandler({
|
|
324
|
+
const handled = frameHandler({
|
|
267
325
|
deviceId: session.deviceId,
|
|
268
326
|
sessionId: session.sessionId,
|
|
269
327
|
header: {
|
|
@@ -276,11 +334,36 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
276
334
|
},
|
|
277
335
|
payload: complete.payload,
|
|
278
336
|
transport: 'udp-p2p'
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
337
|
+
});
|
|
338
|
+
const accepted = handled === true || handled?.accepted === true;
|
|
339
|
+
const proofId = safeText(complete.header?.frameTransportProofId, 96);
|
|
340
|
+
if (proofId) {
|
|
341
|
+
if (handled?.suppressProofResponse === true) return;
|
|
342
|
+
const proofAccepted = accepted && handled?.proofAccepted === true;
|
|
343
|
+
sendPacket(session, 'pong', {
|
|
344
|
+
state: proofAccepted ? 'frame-proof-ack' : 'frame-proof-reject',
|
|
345
|
+
proofId,
|
|
346
|
+
protocol: 'livedesk.frame-transport-proof.v1',
|
|
347
|
+
streamId: safeText(complete.header?.streamId, 128),
|
|
348
|
+
commandId: safeText(complete.header?.commandId, 128),
|
|
349
|
+
streamPurpose: safeText(complete.header?.streamPurpose, 24),
|
|
350
|
+
captureGeneration: Number(complete.header?.captureGeneration || 0) || 0,
|
|
351
|
+
monitorIndex: Number(complete.header?.monitorIndex ?? -1),
|
|
352
|
+
error: proofAccepted ? '' : safeText(handled?.error || 'frame-proof-rejected', 160)
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (accepted) {
|
|
357
|
+
sendPacket(session, 'pong', {
|
|
358
|
+
state: 'frame-ack',
|
|
359
|
+
frameSeq: Number(complete.header?.frameSeq || 0) || 0,
|
|
360
|
+
streamId: safeText(complete.header?.streamId, 128),
|
|
361
|
+
commandId: safeText(complete.header?.commandId, 128),
|
|
362
|
+
streamPurpose: safeText(complete.header?.streamPurpose, 24),
|
|
363
|
+
captureGeneration: Number(complete.header?.captureGeneration || 0) || 0,
|
|
364
|
+
monitorIndex: Number(complete.header?.monitorIndex ?? -1)
|
|
365
|
+
});
|
|
366
|
+
}
|
|
284
367
|
});
|
|
285
368
|
socket.on('error', error => logWarn('udp', `UDP socket error: ${error?.message || error}`));
|
|
286
369
|
|
|
@@ -304,19 +387,35 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
304
387
|
logEvent('udp', `UDP P2P socket listening on udp://${bindHost}:${boundPort}`);
|
|
305
388
|
void refreshRendezvousRegistrations('startup');
|
|
306
389
|
startRendezvousRefreshTimer();
|
|
390
|
+
startFrameExpiryTimer();
|
|
307
391
|
return getStatus();
|
|
308
392
|
}
|
|
309
393
|
|
|
310
|
-
function registerDevice({ deviceId, sessionId: tcpSessionId, sendControl, capabilities } = {}) {
|
|
311
|
-
if (!enabled || capabilities?.udpP2p !== true || !started) return null;
|
|
312
|
-
const id = safeText(deviceId, 160);
|
|
313
|
-
if (!id) return null;
|
|
314
|
-
sessions.get(id)
|
|
315
|
-
|
|
316
|
-
|
|
394
|
+
function registerDevice({ deviceId, sessionId: tcpSessionId, sendControl, capabilities } = {}) {
|
|
395
|
+
if (!enabled || capabilities?.udpP2p !== true || !started) return null;
|
|
396
|
+
const id = safeText(deviceId, 160);
|
|
397
|
+
if (!id) return null;
|
|
398
|
+
const previous = sessions.get(id);
|
|
399
|
+
previous?.sendControl?.({ type: 'udp.session.close', reason: 'replaced' });
|
|
400
|
+
if (previous) {
|
|
401
|
+
sessions.delete(id);
|
|
402
|
+
sessionsBySessionId.delete(previous.sessionId);
|
|
403
|
+
sessionsByRouteKey.delete(previous.routeKey);
|
|
404
|
+
previous.reassembler.clear();
|
|
405
|
+
}
|
|
406
|
+
const key = createUdpSessionKey();
|
|
407
|
+
let udpSessionId;
|
|
408
|
+
let routeKey;
|
|
409
|
+
do {
|
|
410
|
+
udpSessionId = crypto.randomUUID();
|
|
411
|
+
routeKey = udpSessionRouteKey(udpSessionId);
|
|
412
|
+
} while (sessionsByRouteKey.has(routeKey));
|
|
413
|
+
const session = {
|
|
317
414
|
deviceId: id,
|
|
318
415
|
tcpSessionId: safeText(tcpSessionId, 160),
|
|
319
|
-
sessionId:
|
|
416
|
+
sessionId: udpSessionId,
|
|
417
|
+
sessionIdBytes: Buffer.from(udpSessionId, 'utf8'),
|
|
418
|
+
routeKey,
|
|
320
419
|
key,
|
|
321
420
|
keyBase64: key.toString('base64'),
|
|
322
421
|
rendezvousToken: crypto.randomBytes(16).toString('hex'),
|
|
@@ -326,14 +425,22 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
326
425
|
hubRendezvousEndpoint: null,
|
|
327
426
|
hubRendezvousObservedAt: 0,
|
|
328
427
|
replay: new UdpReplayWindow(),
|
|
329
|
-
reassembler: new UdpFrameReassembler({ maxPending: maxPendingFrames, timeoutMs: frameTimeoutMs }),
|
|
330
|
-
|
|
428
|
+
reassembler: new UdpFrameReassembler({ maxPending: maxPendingFrames, timeoutMs: frameTimeoutMs }),
|
|
429
|
+
resource: {
|
|
430
|
+
binaryDatagramsReceived: 0,
|
|
431
|
+
decryptAttempts: 0,
|
|
432
|
+
authenticatedDatagrams: 0,
|
|
433
|
+
rejectedDatagrams: 0
|
|
434
|
+
},
|
|
435
|
+
ready: false,
|
|
331
436
|
createdAt: Date.now(),
|
|
332
437
|
lastPacketAt: 0,
|
|
333
438
|
framesReceived: 0
|
|
334
|
-
};
|
|
335
|
-
sessions.set(id, session);
|
|
336
|
-
|
|
439
|
+
};
|
|
440
|
+
sessions.set(id, session);
|
|
441
|
+
sessionsBySessionId.set(session.sessionId, session);
|
|
442
|
+
sessionsByRouteKey.set(session.routeKey, session);
|
|
443
|
+
sendControl?.({
|
|
337
444
|
type: 'udp.session.offer',
|
|
338
445
|
protocol: UDP_P2P_PROTOCOL,
|
|
339
446
|
sessionId: session.sessionId,
|
|
@@ -342,8 +449,9 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
342
449
|
port: boundPort,
|
|
343
450
|
expiresInMs: punchTimeoutMs + 15_000,
|
|
344
451
|
punchTimeoutMs,
|
|
345
|
-
maxDatagramBytes: UDP_MAX_DATAGRAM_BYTES,
|
|
346
|
-
|
|
452
|
+
maxDatagramBytes: UDP_MAX_DATAGRAM_BYTES,
|
|
453
|
+
frameChunkHeader: UDP_FRAME_CHUNK_PROTOCOL,
|
|
454
|
+
frameTimeoutMs,
|
|
347
455
|
maxPendingFrames,
|
|
348
456
|
rendezvous: rendezvousHost
|
|
349
457
|
? { host: rendezvousHost, port: rendezvousPort, roomId: session.sessionId, token: session.rendezvousToken }
|
|
@@ -354,12 +462,15 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
354
462
|
return { sessionId: session.sessionId, ready: false };
|
|
355
463
|
}
|
|
356
464
|
|
|
357
|
-
function unregisterDevice(deviceId, tcpSessionId = '') {
|
|
465
|
+
function unregisterDevice(deviceId, tcpSessionId = '') {
|
|
358
466
|
const id = safeText(deviceId, 160);
|
|
359
467
|
const session = sessions.get(id);
|
|
360
|
-
if (!session || (tcpSessionId && session.tcpSessionId !== tcpSessionId)) return;
|
|
361
|
-
sessions.delete(id);
|
|
362
|
-
|
|
468
|
+
if (!session || (tcpSessionId && session.tcpSessionId !== tcpSessionId)) return;
|
|
469
|
+
sessions.delete(id);
|
|
470
|
+
sessionsBySessionId.delete(session.sessionId);
|
|
471
|
+
sessionsByRouteKey.delete(session.routeKey);
|
|
472
|
+
session.reassembler.clear();
|
|
473
|
+
emit('UdpSessionClosed', { deviceId: id, reason: 'tcp-session-closed' });
|
|
363
474
|
}
|
|
364
475
|
|
|
365
476
|
function getDeviceStatus(deviceId) {
|
|
@@ -373,6 +484,12 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
373
484
|
sessionId: session.sessionId,
|
|
374
485
|
lastPacketAt: session.lastPacketAt ? new Date(session.lastPacketAt).toISOString() : '',
|
|
375
486
|
framesReceived: session.framesReceived,
|
|
487
|
+
resource: {
|
|
488
|
+
...session.resource,
|
|
489
|
+
binaryJsonParseAttempts: 0,
|
|
490
|
+
sessionLookup: 'map-o1',
|
|
491
|
+
linearSessionScanCount: 0
|
|
492
|
+
},
|
|
376
493
|
...session.reassembler.getStats()
|
|
377
494
|
};
|
|
378
495
|
}
|
|
@@ -444,7 +561,11 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
444
561
|
|
|
445
562
|
async function close() {
|
|
446
563
|
stopRendezvousRefreshTimer();
|
|
564
|
+
stopFrameExpiryTimer();
|
|
565
|
+
for (const session of sessions.values()) session.reassembler.clear();
|
|
447
566
|
sessions.clear();
|
|
567
|
+
sessionsBySessionId.clear();
|
|
568
|
+
sessionsByRouteKey.clear();
|
|
448
569
|
if (!started) return;
|
|
449
570
|
started = false;
|
|
450
571
|
await new Promise(resolve => socket.close(() => resolve()));
|
|
@@ -463,8 +584,45 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
463
584
|
setEventHandler: handler => { eventHandler = typeof handler === 'function' ? handler : () => {}; }
|
|
464
585
|
};
|
|
465
586
|
|
|
466
|
-
function getStatus() {
|
|
467
|
-
|
|
587
|
+
function getStatus() {
|
|
588
|
+
let readySessionCount = 0;
|
|
589
|
+
const reassembly = {
|
|
590
|
+
pendingFrames: 0,
|
|
591
|
+
pendingBytes: 0,
|
|
592
|
+
pendingChunkBuffers: 0,
|
|
593
|
+
pendingBytesHighWatermark: 0,
|
|
594
|
+
pendingChunkBuffersHighWatermark: 0,
|
|
595
|
+
droppedFrames: 0,
|
|
596
|
+
completedFrames: 0,
|
|
597
|
+
completedPayloadCopyCount: 0,
|
|
598
|
+
completedPayloadCopyBytes: 0,
|
|
599
|
+
fullWireDuplicateCopyCount: 0,
|
|
600
|
+
binaryChunkHeaderCount: 0,
|
|
601
|
+
legacyChunkHeaderCount: 0
|
|
602
|
+
};
|
|
603
|
+
for (const session of sessions.values()) {
|
|
604
|
+
if (session.ready) readySessionCount += 1;
|
|
605
|
+
const stats = session.reassembler.getStats();
|
|
606
|
+
reassembly.pendingFrames += stats.pendingFrames;
|
|
607
|
+
reassembly.pendingBytes += stats.pendingBytes;
|
|
608
|
+
reassembly.pendingChunkBuffers += stats.pendingChunkBuffers;
|
|
609
|
+
reassembly.pendingBytesHighWatermark = Math.max(
|
|
610
|
+
reassembly.pendingBytesHighWatermark,
|
|
611
|
+
stats.pendingBytesHighWatermark
|
|
612
|
+
);
|
|
613
|
+
reassembly.pendingChunkBuffersHighWatermark = Math.max(
|
|
614
|
+
reassembly.pendingChunkBuffersHighWatermark,
|
|
615
|
+
stats.pendingChunkBuffersHighWatermark
|
|
616
|
+
);
|
|
617
|
+
reassembly.droppedFrames += stats.droppedFrames;
|
|
618
|
+
reassembly.completedFrames += stats.completedFrames;
|
|
619
|
+
reassembly.completedPayloadCopyCount += stats.completedPayloadCopyCount;
|
|
620
|
+
reassembly.completedPayloadCopyBytes += stats.completedPayloadCopyBytes;
|
|
621
|
+
reassembly.fullWireDuplicateCopyCount += stats.fullWireDuplicateCopyCount;
|
|
622
|
+
reassembly.binaryChunkHeaderCount += stats.binaryChunkHeaderCount;
|
|
623
|
+
reassembly.legacyChunkHeaderCount += stats.legacyChunkHeaderCount;
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
468
626
|
enabled,
|
|
469
627
|
preferred: preferP2p,
|
|
470
628
|
started,
|
|
@@ -475,9 +633,15 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
475
633
|
rendezvousPort,
|
|
476
634
|
rendezvousRefreshMs,
|
|
477
635
|
rendezvousResolvedAddressCount: rendezvousAddresses.size,
|
|
478
|
-
maxDatagramBytes: UDP_MAX_DATAGRAM_BYTES,
|
|
479
|
-
sessionCount: sessions.size,
|
|
480
|
-
readySessionCount
|
|
481
|
-
|
|
636
|
+
maxDatagramBytes: UDP_MAX_DATAGRAM_BYTES,
|
|
637
|
+
sessionCount: sessions.size,
|
|
638
|
+
readySessionCount,
|
|
639
|
+
resource: {
|
|
640
|
+
...resource,
|
|
641
|
+
indexedSessionCount: sessionsBySessionId.size,
|
|
642
|
+
indexedRouteCount: sessionsByRouteKey.size,
|
|
643
|
+
...reassembly
|
|
644
|
+
}
|
|
645
|
+
};
|
|
482
646
|
}
|
|
483
647
|
}
|