@livedesk/hub 0.1.28 → 0.1.30

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -11,11 +11,12 @@
11
11
  "scripts": {
12
12
  "dev": "node src/server.js",
13
13
  "start": "node src/server.js",
14
- "check": "node --check src/server.js && node --check src/remote-hub.js"
14
+ "check": "node --check src/server.js && node --check src/remote-hub.js",
15
+ "prepublishOnly": "node ../../scripts/livedesk-release-git-gate.mjs"
15
16
  },
16
17
  "dependencies": {
17
18
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
18
- "@livedesk/runtime-core": "0.1.1",
19
+ "@livedesk/runtime-core": "0.1.2",
19
20
  "@openai/codex-sdk": "0.145.0",
20
21
  "cors": "^2.8.5",
21
22
  "express": "^4.21.2",
@@ -511,6 +511,7 @@ export function createHubRelayControl({
511
511
  let timerOwnerHighWater = 0;
512
512
  let awaitingDrain = false;
513
513
  let lastError = '';
514
+ let lastDisconnectReason = '';
514
515
  let lastConnectedAt = '';
515
516
  let lastTransitionAt = '';
516
517
  let state = enabled ? 'idle' : 'disabled';
@@ -982,8 +983,15 @@ export function createHubRelayControl({
982
983
  return;
983
984
  }
984
985
  if (message.type === 'relay.close') {
986
+ const reason = safeText(message.reason, 80) || 'relay-server-closed';
987
+ if (!peerId) {
988
+ lastError = reason;
989
+ lastDisconnectReason = reason;
990
+ connection?.destroy();
991
+ return;
992
+ }
985
993
  try {
986
- closePeer(requirePeerId(message.peerId), 'relay-client-closed', { notify: false });
994
+ closePeer(requirePeerId(message.peerId), reason, { notify: false });
987
995
  } catch {
988
996
  // Invalid untrusted routing fields are ignored.
989
997
  }
@@ -1121,6 +1129,12 @@ export function createHubRelayControl({
1121
1129
  if (connection !== socket || generation !== connectionGeneration) return;
1122
1130
  clearConnectAttemptTimer();
1123
1131
  connection = null;
1132
+ const disconnectReason = safeText(lastError, 120) || 'relay-socket-closed';
1133
+ lastDisconnectReason = disconnectReason;
1134
+ logWarn(
1135
+ 'relay',
1136
+ `Encrypted relay control disconnected reason=${disconnectReason} peers=${peers.size}`
1137
+ );
1124
1138
  clearReceiveChunks();
1125
1139
  clearQueue();
1126
1140
  closeAllPeers('relay-connection-lost');
@@ -1340,6 +1354,7 @@ export function createHubRelayControl({
1340
1354
  queueDropCount: counters.queueDrops,
1341
1355
  lastConnectedAt,
1342
1356
  lastTransitionAt,
1357
+ lastDisconnectReason,
1343
1358
  lastError
1344
1359
  };
1345
1360
  }
@@ -96,7 +96,8 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
96
96
  let rendezvousAddresses = new Set();
97
97
  let rendezvousRefreshTimer = null;
98
98
  let rendezvousRefreshPromise = null;
99
- let frameExpiryTimer = null;
99
+ let frameExpiryTimer = null;
100
+ const peerProbeBurstDelaysMs = [0, 120, 360];
100
101
 
101
102
  function emit(type, event = {}) {
102
103
  eventHandler(type, event);
@@ -230,6 +231,33 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
230
231
  return true;
231
232
  }
232
233
 
234
+ function clearPeerProbeTimers(session) {
235
+ if (!session?.peerProbeTimers) return;
236
+ for (const timer of session.peerProbeTimers) clearTimeout(timer);
237
+ session.peerProbeTimers.clear();
238
+ }
239
+
240
+ function schedulePeerProbeBurst(session, endpoint) {
241
+ clearPeerProbeTimers(session);
242
+ for (const delayMs of peerProbeBurstDelaysMs) {
243
+ const sendProbe = () => {
244
+ if (sessions.get(session.deviceId) !== session
245
+ || !sameEndpoint(session.peerEndpoint, endpoint)) return;
246
+ sendPacket(session, 'probe', { punch: true }, Buffer.alloc(0), endpoint);
247
+ };
248
+ if (delayMs === 0) {
249
+ sendProbe();
250
+ continue;
251
+ }
252
+ const timer = setTimeout(() => {
253
+ session.peerProbeTimers.delete(timer);
254
+ sendProbe();
255
+ }, delayMs);
256
+ timer.unref?.();
257
+ session.peerProbeTimers.add(timer);
258
+ }
259
+ }
260
+
233
261
  function onRendezvousPeer(message, rinfo) {
234
262
  const session = sessionsBySessionId.get(safeText(message.roomId, 160));
235
263
  if (!session || message.protocol !== UDP_P2P_PROTOCOL || !isTrustedRendezvousSource(rinfo)) return;
@@ -248,9 +276,9 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
248
276
  };
249
277
  session.hubRendezvousObservedAt = Date.now();
250
278
  }
251
- session.peerEndpoint = { address, port };
279
+ session.peerEndpoint = { address, port };
252
280
  session.lastPeerAt = Date.now();
253
- sendPacket(session, 'probe', { punch: true }, Buffer.alloc(0), session.peerEndpoint);
281
+ schedulePeerProbeBurst(session, session.peerEndpoint);
254
282
  emit('UdpPeerEndpointObserved', { deviceId: session.deviceId, endpoint: `${address}:${port}` });
255
283
  }
256
284
 
@@ -298,7 +326,8 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
298
326
  emit('UdpProbeReceived', { deviceId: session.deviceId });
299
327
  return;
300
328
  }
301
- if (packet.type === 'ready') {
329
+ if (packet.type === 'ready') {
330
+ clearPeerProbeTimers(session);
302
331
  session.ready = true;
303
332
  sendPacket(session, 'pong', { state: 'ready' });
304
333
  session.sendControl?.({
@@ -396,8 +425,9 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
396
425
  const id = safeText(deviceId, 160);
397
426
  if (!id) return null;
398
427
  const previous = sessions.get(id);
399
- previous?.sendControl?.({ type: 'udp.session.close', reason: 'replaced' });
400
- if (previous) {
428
+ previous?.sendControl?.({ type: 'udp.session.close', reason: 'replaced' });
429
+ if (previous) {
430
+ clearPeerProbeTimers(previous);
401
431
  sessions.delete(id);
402
432
  sessionsBySessionId.delete(previous.sessionId);
403
433
  sessionsByRouteKey.delete(previous.routeKey);
@@ -432,7 +462,8 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
432
462
  authenticatedDatagrams: 0,
433
463
  rejectedDatagrams: 0
434
464
  },
435
- ready: false,
465
+ ready: false,
466
+ peerProbeTimers: new Set(),
436
467
  createdAt: Date.now(),
437
468
  lastPacketAt: 0,
438
469
  framesReceived: 0
@@ -466,7 +497,8 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
466
497
  const id = safeText(deviceId, 160);
467
498
  const session = sessions.get(id);
468
499
  if (!session || (tcpSessionId && session.tcpSessionId !== tcpSessionId)) return;
469
- sessions.delete(id);
500
+ clearPeerProbeTimers(session);
501
+ sessions.delete(id);
470
502
  sessionsBySessionId.delete(session.sessionId);
471
503
  sessionsByRouteKey.delete(session.routeKey);
472
504
  session.reassembler.clear();
@@ -562,7 +594,10 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
562
594
  async function close() {
563
595
  stopRendezvousRefreshTimer();
564
596
  stopFrameExpiryTimer();
565
- for (const session of sessions.values()) session.reassembler.clear();
597
+ for (const session of sessions.values()) {
598
+ clearPeerProbeTimers(session);
599
+ session.reassembler.clear();
600
+ }
566
601
  sessions.clear();
567
602
  sessionsBySessionId.clear();
568
603
  sessionsByRouteKey.clear();