livedesk 0.1.458 → 0.1.460

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.
@@ -31,6 +31,17 @@ const LIVE_STREAM_MIN_FRESH_MS = 3000;
31
31
  const LIVE_STREAM_MAX_FRESH_MS = 12000;
32
32
  const DEFAULT_REMOTE_INPUT_ACK_TIMEOUT_MS = 5000;
33
33
  const DEFAULT_LIVE_CAPTURE_STOP_ACK_TIMEOUT_MS = 8000;
34
+ const TRANSPORT_DIAGNOSTIC_MIN_TTL_MS = 15_000;
35
+ const TRANSPORT_DIAGNOSTIC_MAX_TTL_MS = 180_000;
36
+ const TRANSPORT_DIAGNOSTIC_COMMAND_TIMEOUT_MS = 8_000;
37
+ const TRANSPORT_DIAGNOSTIC_RECORD_RETENTION_MS = 60_000;
38
+ const TRANSPORT_DIAGNOSTIC_MAX_RECORDS = 32;
39
+ const TRANSPORT_DIAGNOSTIC_PHASES = Object.freeze([
40
+ 'direct',
41
+ 'p2p',
42
+ 'relay',
43
+ 'recover-direct'
44
+ ]);
34
45
  const REMOTE_POLICY_BYPASS_COMMANDS = new Set([
35
46
  'ping',
36
47
  'stream.stop',
@@ -429,13 +440,23 @@ function isPublicIPv4(value) {
429
440
  return false;
430
441
  }
431
442
 
432
- if (parts[0] === 192 && (parts[1] === 0 || parts[1] === 168)) {
433
- return false;
434
- }
435
-
436
- if (parts[0] === 198 && (parts[1] === 18 || parts[1] === 19)) {
437
- return false;
438
- }
443
+ if (parts[0] === 192
444
+ && (parts[1] === 0
445
+ || parts[1] === 168
446
+ || (parts[1] === 88 && parts[2] === 99))) {
447
+ return false;
448
+ }
449
+
450
+ if (parts[0] === 198
451
+ && (parts[1] === 18
452
+ || parts[1] === 19
453
+ || (parts[1] === 51 && parts[2] === 100))) {
454
+ return false;
455
+ }
456
+
457
+ if (parts[0] === 203 && parts[1] === 0 && parts[2] === 113) {
458
+ return false;
459
+ }
439
460
 
440
461
  return true;
441
462
  }
@@ -1528,6 +1549,11 @@ class RemoteHubWebSocketAgentSocket {
1528
1549
 
1529
1550
  export function createRemoteHub(options = {}) {
1530
1551
  const env = options.env || process.env;
1552
+ const transportDiagnosticCommandTimeoutMs = clampNumber(
1553
+ options.transportDiagnosticCommandTimeoutMs,
1554
+ 25,
1555
+ TRANSPORT_DIAGNOSTIC_COMMAND_TIMEOUT_MS,
1556
+ TRANSPORT_DIAGNOSTIC_COMMAND_TIMEOUT_MS);
1531
1557
  const rejectFrameChannelForTest = isEnabledValue(env.LIVEDESK_TEST_MODE, false)
1532
1558
  && isEnabledValue(env.LIVEDESK_TEST_REJECT_FRAME_CHANNEL, false);
1533
1559
  const logEvent = options.logEvent || (() => {});
@@ -1631,7 +1657,9 @@ export function createRemoteHub(options = {}) {
1631
1657
  const fileSockets = new Map();
1632
1658
  const allSockets = new Set();
1633
1659
  const pendingCommandResultWaiters = new Map();
1634
- const duplicateDeviceLogAt = new Map();
1660
+ const transportDiagnostics = new Map();
1661
+ const transportDiagnosticStartingDevices = new Set();
1662
+ const duplicateDeviceLogAt = new Map();
1635
1663
  let server = null;
1636
1664
  let started = false;
1637
1665
  let boundPort = requestedPort;
@@ -2266,6 +2294,700 @@ export function createRemoteHub(options = {}) {
2266
2294
  });
2267
2295
  }
2268
2296
 
2297
+ function inspectAnnexBH264(payload) {
2298
+ if (!Buffer.isBuffer(payload) || payload.length < 5) {
2299
+ return {
2300
+ annexB: false,
2301
+ hasIdr: false,
2302
+ hasSps: false,
2303
+ hasPps: false,
2304
+ nalOrderValid: false
2305
+ };
2306
+ }
2307
+ let annexB = false;
2308
+ let hasIdr = false;
2309
+ let hasSps = false;
2310
+ let hasPps = false;
2311
+ let firstSpsAt = -1;
2312
+ let firstPpsAfterSpsAt = -1;
2313
+ let firstIdrAfterPpsAt = -1;
2314
+ for (let index = 0; index + 4 < payload.length; index += 1) {
2315
+ let headerIndex = -1;
2316
+ if (payload[index] === 0 && payload[index + 1] === 0 && payload[index + 2] === 1) {
2317
+ headerIndex = index + 3;
2318
+ } else if (payload[index] === 0
2319
+ && payload[index + 1] === 0
2320
+ && payload[index + 2] === 0
2321
+ && payload[index + 3] === 1) {
2322
+ headerIndex = index + 4;
2323
+ }
2324
+ if (headerIndex < 0 || headerIndex >= payload.length) continue;
2325
+ annexB = true;
2326
+ const nalType = payload[headerIndex] & 0x1f;
2327
+ if (nalType === 5) {
2328
+ hasIdr = true;
2329
+ if (firstPpsAfterSpsAt >= 0 && firstIdrAfterPpsAt < 0) {
2330
+ firstIdrAfterPpsAt = headerIndex;
2331
+ }
2332
+ }
2333
+ if (nalType === 7) {
2334
+ hasSps = true;
2335
+ if (firstSpsAt < 0) firstSpsAt = headerIndex;
2336
+ }
2337
+ if (nalType === 8) {
2338
+ hasPps = true;
2339
+ if (firstSpsAt >= 0 && firstPpsAfterSpsAt < 0) {
2340
+ firstPpsAfterSpsAt = headerIndex;
2341
+ }
2342
+ }
2343
+ index = headerIndex;
2344
+ }
2345
+ return {
2346
+ annexB,
2347
+ hasIdr,
2348
+ hasSps,
2349
+ hasPps,
2350
+ nalOrderValid: firstSpsAt >= 0
2351
+ && firstPpsAfterSpsAt > firstSpsAt
2352
+ && firstIdrAfterPpsAt > firstPpsAfterSpsAt
2353
+ };
2354
+ }
2355
+
2356
+ function expectedTransportForDiagnosticPhase(phase) {
2357
+ if (phase === 'p2p') return 'p2p';
2358
+ if (phase === 'relay') return 'relay';
2359
+ return 'direct';
2360
+ }
2361
+
2362
+ function observedTransportPath(frame) {
2363
+ const transport = safeString(frame?.transport, 32).toLowerCase();
2364
+ if (transport === 'udp-p2p') return 'p2p';
2365
+ if (transport === 'relay-binary') return 'relay';
2366
+ if (transport === 'binary' || transport === 'ws-binary') return 'direct';
2367
+ return '';
2368
+ }
2369
+
2370
+ function optionalFiniteNumber(value) {
2371
+ return value !== null && value !== undefined && value !== ''
2372
+ && Number.isFinite(Number(value))
2373
+ ? Number(value)
2374
+ : null;
2375
+ }
2376
+
2377
+ function transportDiagnosticIdentifierHash(value, salt) {
2378
+ const normalized = safeString(value, 240);
2379
+ return normalized
2380
+ ? crypto.createHash('sha256')
2381
+ .update(`${safeString(salt, 160)}:${normalized}`)
2382
+ .digest('hex')
2383
+ .slice(0, 16)
2384
+ : '';
2385
+ }
2386
+
2387
+ function currentTransportDiagnosticOwner(device) {
2388
+ const frame = device?.latestLiveFrame;
2389
+ if (!device?.connected
2390
+ || !device.sessionId
2391
+ || !frame
2392
+ || frame.currentGenerationVerified !== true
2393
+ || safeString(frame.mimeType, 80).toLowerCase() !== 'video/h264'
2394
+ || !safeString(frame.streamId, 128)
2395
+ || !safeString(frame.commandId, 128)
2396
+ || Number(frame.captureGeneration || 0) <= 0) {
2397
+ return null;
2398
+ }
2399
+ const monitorIndex = parseExactLiveStreamMonitorIndex(frame.monitorIndex);
2400
+ const streamState = getDeviceLiveStream(device, frame.streamId);
2401
+ if (monitorIndex === null
2402
+ || !streamState?.active
2403
+ || safeString(streamState.commandId, 128) !== safeString(frame.commandId, 128)
2404
+ || Number(streamState.captureGeneration || 0) !== Number(frame.captureGeneration || 0)
2405
+ || parseExactLiveStreamMonitorIndex(streamState.monitorIndex) !== monitorIndex) {
2406
+ return null;
2407
+ }
2408
+ return {
2409
+ deviceId: device.deviceId,
2410
+ sessionId: device.sessionId,
2411
+ streamId: safeString(frame.streamId, 128),
2412
+ streamCommandId: safeString(frame.commandId, 128),
2413
+ captureGeneration: Number(frame.captureGeneration),
2414
+ monitorIndex
2415
+ };
2416
+ }
2417
+
2418
+ function transportDiagnosticOwnersEqual(left, right) {
2419
+ return !!left
2420
+ && !!right
2421
+ && left.deviceId === right.deviceId
2422
+ && left.sessionId === right.sessionId
2423
+ && left.streamId === right.streamId
2424
+ && left.streamCommandId === right.streamCommandId
2425
+ && left.captureGeneration === right.captureGeneration
2426
+ && left.monitorIndex === right.monitorIndex;
2427
+ }
2428
+
2429
+ function transportDiagnosticOwnerMatches(record, device) {
2430
+ const current = currentTransportDiagnosticOwner(device);
2431
+ return transportDiagnosticOwnersEqual(current, record.owner);
2432
+ }
2433
+
2434
+ function serializeTransportDiagnostic(record) {
2435
+ const refreshedNetwork = udpTransport?.getDeviceDiagnosticNetwork?.(record.owner.deviceId, {
2436
+ salt: record.leaseId
2437
+ });
2438
+ if (refreshedNetwork?.evidence
2439
+ === 'authenticated-udp-endpoint-rendezvous-observed'
2440
+ || record.network?.evidence
2441
+ !== 'authenticated-udp-endpoint-rendezvous-observed') {
2442
+ record.network = refreshedNetwork || record.network;
2443
+ }
2444
+ return {
2445
+ ok: record.state !== 'failed',
2446
+ leaseId: record.leaseId,
2447
+ state: record.state,
2448
+ phase: record.phase,
2449
+ startedAt: record.startedAt,
2450
+ updatedAt: record.updatedAt,
2451
+ expiresAt: record.expiresAt,
2452
+ completedAt: record.completedAt || '',
2453
+ error: safeString(record.error, 240),
2454
+ owner: {
2455
+ deviceId: record.owner.deviceId,
2456
+ streamId: record.owner.streamId,
2457
+ streamCommandId: record.owner.streamCommandId,
2458
+ captureGeneration: record.owner.captureGeneration,
2459
+ monitorIndex: record.owner.monitorIndex
2460
+ },
2461
+ network: record.network,
2462
+ baseline: { ...record.baseline },
2463
+ frames: record.frames.map(frame => ({ ...frame })),
2464
+ cleanup: record.cleanup.map(entry => ({ ...entry }))
2465
+ };
2466
+ }
2467
+
2468
+ function getTransportDiagnostic(leaseId, token) {
2469
+ const record = transportDiagnostics.get(safeString(leaseId, 128));
2470
+ if (!record || !diagnosticSecretEquals(record.token, token)) {
2471
+ return { ok: false, error: 'transport-diagnostic-not-found' };
2472
+ }
2473
+ return serializeTransportDiagnostic(record);
2474
+ }
2475
+
2476
+ function diagnosticSecretEquals(expected, provided) {
2477
+ const expectedHash = crypto.createHash('sha256').update(String(expected || '')).digest();
2478
+ const providedHash = crypto.createHash('sha256').update(String(provided || '')).digest();
2479
+ return crypto.timingSafeEqual(expectedHash, providedHash);
2480
+ }
2481
+
2482
+ async function sendTransportDiagnosticCommand(record, operation, phase = record.phase) {
2483
+ const device = devices.get(record.owner.deviceId);
2484
+ if (!device?.connected || device.sessionId !== record.owner.sessionId) {
2485
+ return { ok: false, error: 'transport-diagnostic-device-disconnected' };
2486
+ }
2487
+ if (operation !== 'release' && !transportDiagnosticOwnerMatches(record, device)) {
2488
+ return { ok: false, error: 'transport-diagnostic-owner-changed' };
2489
+ }
2490
+ const commandId = crypto.randomUUID();
2491
+ const waiter = waitForCommandResult(device, commandId, transportDiagnosticCommandTimeoutMs);
2492
+ const queued = sendCommand(device.deviceId, {
2493
+ commandId,
2494
+ command: 'transport.diagnostic',
2495
+ payload: {
2496
+ operation,
2497
+ leaseId: record.leaseId,
2498
+ token: record.token,
2499
+ streamId: record.owner.streamId,
2500
+ streamCommandId: record.owner.streamCommandId,
2501
+ captureGeneration: record.owner.captureGeneration,
2502
+ monitorIndex: record.owner.monitorIndex,
2503
+ phase,
2504
+ ttlMs: Math.max(0, Date.parse(record.expiresAt) - Date.now())
2505
+ }
2506
+ });
2507
+ if (!queued.ok) {
2508
+ failPendingCommandResultWaiter(commandId, queued.error);
2509
+ }
2510
+ const outcome = await waiter;
2511
+ if (!outcome.ok || outcome.result?.ok === false) {
2512
+ return {
2513
+ ok: false,
2514
+ error: safeString(outcome.error || outcome.result?.error, 240)
2515
+ || 'transport-diagnostic-agent-rejected'
2516
+ };
2517
+ }
2518
+ return { ok: true };
2519
+ }
2520
+
2521
+ function appendTransportDiagnosticCleanup(record, state, reason = '') {
2522
+ record.cleanup.push({
2523
+ at: new Date().toISOString(),
2524
+ state,
2525
+ reason: safeString(reason, 160)
2526
+ });
2527
+ if (record.cleanup.length > 16) record.cleanup.splice(0, record.cleanup.length - 16);
2528
+ record.updatedAt = new Date().toISOString();
2529
+ }
2530
+
2531
+ function clearTransportDiagnosticTimers(record) {
2532
+ clearTimeout(record?.expiryTimer);
2533
+ clearTimeout(record?.removalTimer);
2534
+ if (record) {
2535
+ record.expiryTimer = null;
2536
+ record.removalTimer = null;
2537
+ }
2538
+ }
2539
+
2540
+ function removeTransportDiagnostic(record) {
2541
+ if (!record) return;
2542
+ clearTransportDiagnosticTimers(record);
2543
+ if (transportDiagnostics.get(record.leaseId) === record) {
2544
+ transportDiagnostics.delete(record.leaseId);
2545
+ }
2546
+ record.token = '';
2547
+ }
2548
+
2549
+ function scheduleTransportDiagnosticRemoval(record) {
2550
+ if (!record) return;
2551
+ clearTimeout(record.expiryTimer);
2552
+ clearTimeout(record.removalTimer);
2553
+ record.expiryTimer = null;
2554
+ record.removalTimer = setTimeout(() => {
2555
+ removeTransportDiagnostic(record);
2556
+ }, TRANSPORT_DIAGNOSTIC_RECORD_RETENTION_MS);
2557
+ record.removalTimer.unref?.();
2558
+ }
2559
+
2560
+ function pruneTransportDiagnosticRecords() {
2561
+ const finalized = [...transportDiagnostics.values()]
2562
+ .filter(record => record.state !== 'active'
2563
+ && record.state !== 'acquiring'
2564
+ && record.state !== 'expiring')
2565
+ .sort((left, right) => Date.parse(left.updatedAt || '') - Date.parse(right.updatedAt || ''));
2566
+ while (transportDiagnostics.size >= TRANSPORT_DIAGNOSTIC_MAX_RECORDS && finalized.length > 0) {
2567
+ removeTransportDiagnostic(finalized.shift());
2568
+ }
2569
+ }
2570
+
2571
+ async function expireTransportDiagnostic(record, reason = 'ttl-expired') {
2572
+ if (!record || record.state !== 'active') return;
2573
+ if (record.operationInFlight === true) {
2574
+ record.expiryTimer = setTimeout(() => {
2575
+ void expireTransportDiagnostic(record, reason);
2576
+ }, 250);
2577
+ record.expiryTimer.unref?.();
2578
+ return;
2579
+ }
2580
+ record.operationInFlight = true;
2581
+ record.state = 'expiring';
2582
+ appendTransportDiagnosticCleanup(record, 'release-requested', reason);
2583
+ try {
2584
+ const released = await sendTransportDiagnosticCommand(record, 'release');
2585
+ record.state = reason === 'ttl-expired' ? 'expired' : 'aborted';
2586
+ record.error = released.ok ? '' : released.error;
2587
+ record.completedAt = new Date().toISOString();
2588
+ appendTransportDiagnosticCleanup(
2589
+ record,
2590
+ released.ok ? 'agent-released' : 'agent-release-unconfirmed',
2591
+ released.error);
2592
+ } finally {
2593
+ record.operationInFlight = false;
2594
+ scheduleTransportDiagnosticRemoval(record);
2595
+ }
2596
+ }
2597
+
2598
+ function abortTransportDiagnosticsForDevice(device, reason) {
2599
+ for (const record of transportDiagnostics.values()) {
2600
+ if (!['active', 'acquiring', 'expiring'].includes(record.state)
2601
+ || record.owner.deviceId !== device?.deviceId
2602
+ || record.owner.sessionId !== device?.sessionId) {
2603
+ continue;
2604
+ }
2605
+ clearTimeout(record.expiryTimer);
2606
+ record.state = 'aborted';
2607
+ record.error = safeString(reason, 160) || 'device-disconnected';
2608
+ record.completedAt = new Date().toISOString();
2609
+ appendTransportDiagnosticCleanup(record, 'connection-closed', record.error);
2610
+ scheduleTransportDiagnosticRemoval(record);
2611
+ }
2612
+ }
2613
+
2614
+ async function startTransportDiagnostic(deviceId, ttlMs = 150_000, options = {}) {
2615
+ const requestSignal = options?.signal;
2616
+ const requestAborted = () => requestSignal?.aborted === true;
2617
+ const normalizedDeviceId = safeString(deviceId, 160);
2618
+ const device = devices.get(normalizedDeviceId);
2619
+ const owner = currentTransportDiagnosticOwner(device);
2620
+ if (!owner) {
2621
+ return { ok: false, error: 'transport-diagnostic-current-h264-stream-required' };
2622
+ }
2623
+ if (observedTransportPath(device.latestLiveFrame) !== 'direct'
2624
+ || safeString(device.latestLiveFrame?.frameTransportActive, 20).toLowerCase() !== 'direct') {
2625
+ return { ok: false, error: 'transport-diagnostic-direct-start-required' };
2626
+ }
2627
+ if (transportDiagnosticStartingDevices.has(normalizedDeviceId)) {
2628
+ return { ok: false, error: 'transport-diagnostic-device-lease-active' };
2629
+ }
2630
+ const existingLease = [...transportDiagnostics.values()].some(existing =>
2631
+ ['active', 'acquiring', 'expiring'].includes(existing.state)
2632
+ && existing.owner.deviceId === owner.deviceId);
2633
+ if (existingLease) {
2634
+ return { ok: false, error: 'transport-diagnostic-device-lease-active' };
2635
+ }
2636
+ transportDiagnosticStartingDevices.add(normalizedDeviceId);
2637
+ try {
2638
+ const refreshedDiagnosticNetwork =
2639
+ await udpTransport?.refreshDeviceDiagnosticNetwork?.(
2640
+ owner.deviceId,
2641
+ { timeoutMs: 3_000 });
2642
+ if (requestAborted()) {
2643
+ return { ok: false, error: 'transport-diagnostic-request-aborted' };
2644
+ }
2645
+ const physicalNetwork = udpTransport?.getDeviceDiagnosticNetwork?.(
2646
+ owner.deviceId,
2647
+ { salt: 'transport-diagnostic-preflight' }) || {};
2648
+ if (refreshedDiagnosticNetwork !== true
2649
+ || physicalNetwork.evidence
2650
+ !== 'authenticated-udp-endpoint-rendezvous-observed'
2651
+ || physicalNetwork.endpointAuthenticated !== true
2652
+ || physicalNetwork.clientAddressPublic !== true
2653
+ || physicalNetwork.hubAddressPublic !== true) {
2654
+ return {
2655
+ ok: false,
2656
+ error: 'transport-diagnostic-fresh-rendezvous-egress-required'
2657
+ };
2658
+ }
2659
+ if (physicalNetwork.differentPublicEgress !== true) {
2660
+ return {
2661
+ ok: false,
2662
+ error: 'transport-diagnostic-distinct-public-egress-required'
2663
+ };
2664
+ }
2665
+ const currentDevice = devices.get(normalizedDeviceId);
2666
+ const refreshedOwner = currentTransportDiagnosticOwner(currentDevice);
2667
+ if (!transportDiagnosticOwnersEqual(owner, refreshedOwner)
2668
+ || observedTransportPath(currentDevice?.latestLiveFrame) !== 'direct'
2669
+ || safeString(currentDevice?.latestLiveFrame?.frameTransportActive, 20).toLowerCase() !== 'direct') {
2670
+ return { ok: false, error: 'transport-diagnostic-owner-changed' };
2671
+ }
2672
+ const concurrentLease = [...transportDiagnostics.values()].some(existing =>
2673
+ ['active', 'acquiring', 'expiring'].includes(existing.state)
2674
+ && existing.owner.deviceId === owner.deviceId);
2675
+ if (concurrentLease) {
2676
+ return { ok: false, error: 'transport-diagnostic-device-lease-active' };
2677
+ }
2678
+ pruneTransportDiagnosticRecords();
2679
+ if (transportDiagnostics.size >= TRANSPORT_DIAGNOSTIC_MAX_RECORDS) {
2680
+ return { ok: false, error: 'transport-diagnostic-capacity-reached' };
2681
+ }
2682
+
2683
+ const boundedTtlMs = clampNumber(
2684
+ ttlMs,
2685
+ TRANSPORT_DIAGNOSTIC_MIN_TTL_MS,
2686
+ TRANSPORT_DIAGNOSTIC_MAX_TTL_MS,
2687
+ 150_000);
2688
+ const now = new Date();
2689
+ const leaseId = crypto.randomUUID();
2690
+ const token = crypto.randomBytes(32).toString('base64url');
2691
+ const currentFrame = currentDevice.latestLiveFrame;
2692
+ const udpStatus = udpTransport?.getDeviceStatus?.(owner.deviceId) || {};
2693
+ const hubAddressObservedAt = safeString(
2694
+ physicalNetwork.hubAddressObservedAt,
2695
+ 80);
2696
+ const record = {
2697
+ leaseId,
2698
+ token,
2699
+ owner,
2700
+ phase: 'direct',
2701
+ phaseStartedAt: '',
2702
+ state: 'acquiring',
2703
+ operationInFlight: true,
2704
+ startedAt: now.toISOString(),
2705
+ updatedAt: now.toISOString(),
2706
+ expiresAt: new Date(now.getTime() + boundedTtlMs).toISOString(),
2707
+ completedAt: '',
2708
+ error: '',
2709
+ hubAddressObservedAt,
2710
+ baseline: {
2711
+ observedAt: safeString(currentFrame.receivedAt, 80),
2712
+ frameSeq: optionalFiniteNumber(currentFrame.frameSeq),
2713
+ switchCount: optionalFiniteNumber(currentFrame.frameTransportSwitchCount),
2714
+ agentQueueDropped: optionalFiniteNumber(currentFrame.nativeOutputQueueDroppedCount),
2715
+ hubIngressDropped: optionalFiniteNumber(currentFrame.hubIngressDropped),
2716
+ udpIncompleteFrames: optionalFiniteNumber(udpStatus.droppedFrames),
2717
+ hubIngressCounterEpochHash: transportDiagnosticIdentifierHash(
2718
+ currentFrame.hubIngressCounterEpoch,
2719
+ leaseId),
2720
+ udpCounterEpochHash: transportDiagnosticIdentifierHash(
2721
+ udpStatus.sessionId,
2722
+ leaseId),
2723
+ agentQueueTelemetryAvailable:
2724
+ currentFrame.nativeOutputQueueTelemetryAvailable === true,
2725
+ hubIngressTelemetryAvailable:
2726
+ currentFrame.hubIngressTelemetryAvailable === true
2727
+ && !!currentFrame.hubIngressCounterEpoch,
2728
+ udpTelemetryAvailable:
2729
+ optionalFiniteNumber(udpStatus.droppedFrames) !== null
2730
+ && !!udpStatus.sessionId
2731
+ },
2732
+ frames: [],
2733
+ cleanup: [],
2734
+ network: udpTransport?.getDeviceDiagnosticNetwork?.(owner.deviceId, {
2735
+ salt: leaseId
2736
+ }) || {
2737
+ evidence: 'unavailable',
2738
+ differentPublicEgress: null,
2739
+ hubAddressObservedAt
2740
+ },
2741
+ expiryTimer: null,
2742
+ removalTimer: null
2743
+ };
2744
+ transportDiagnostics.set(leaseId, record);
2745
+ const acquired = await sendTransportDiagnosticCommand(record, 'acquire', 'direct');
2746
+ record.operationInFlight = false;
2747
+ if (!acquired.ok || record.state !== 'acquiring') {
2748
+ const acquireError = acquired.ok
2749
+ ? safeString(record.error, 240) || 'transport-diagnostic-acquire-interrupted'
2750
+ : acquired.error;
2751
+ record.error = acquireError;
2752
+ record.completedAt = new Date().toISOString();
2753
+ appendTransportDiagnosticCleanup(record, 'acquire-failed', record.error);
2754
+ appendTransportDiagnosticCleanup(
2755
+ record,
2756
+ 'release-requested',
2757
+ 'acquire-outcome-uncertain');
2758
+ record.operationInFlight = true;
2759
+ let released;
2760
+ try {
2761
+ released = await sendTransportDiagnosticCommand(record, 'release');
2762
+ } catch (error) {
2763
+ released = {
2764
+ ok: false,
2765
+ error: safeString(error instanceof Error ? error.message : error, 240)
2766
+ || 'transport-diagnostic-agent-release-failed'
2767
+ };
2768
+ } finally {
2769
+ record.operationInFlight = false;
2770
+ }
2771
+ appendTransportDiagnosticCleanup(
2772
+ record,
2773
+ released.ok ? 'agent-released' : 'agent-release-unconfirmed',
2774
+ released.error);
2775
+ record.state = 'failed';
2776
+ record.completedAt = new Date().toISOString();
2777
+ const failed = { ...serializeTransportDiagnostic(record), token: '' };
2778
+ removeTransportDiagnostic(record);
2779
+ return failed;
2780
+ }
2781
+ record.state = 'active';
2782
+ record.phaseStartedAt = new Date().toISOString();
2783
+ record.updatedAt = record.phaseStartedAt;
2784
+ appendTransportDiagnosticCleanup(record, 'lease-acquired');
2785
+ if (requestAborted()) {
2786
+ await expireTransportDiagnostic(record, 'start-request-aborted');
2787
+ return { ok: false, error: 'transport-diagnostic-request-aborted' };
2788
+ }
2789
+ const remainingTtlMs = Math.max(1, Date.parse(record.expiresAt) - Date.now());
2790
+ record.expiryTimer = setTimeout(() => {
2791
+ void expireTransportDiagnostic(record, 'ttl-expired');
2792
+ }, remainingTtlMs);
2793
+ record.expiryTimer.unref?.();
2794
+ return { ...serializeTransportDiagnostic(record), token };
2795
+ } finally {
2796
+ transportDiagnosticStartingDevices.delete(normalizedDeviceId);
2797
+ }
2798
+ }
2799
+
2800
+ async function setTransportDiagnosticPhase(leaseId, token, phase) {
2801
+ const record = transportDiagnostics.get(safeString(leaseId, 128));
2802
+ const normalizedPhase = safeString(phase, 32).toLowerCase();
2803
+ if (!record || !diagnosticSecretEquals(record.token, token)) {
2804
+ return { ok: false, error: 'transport-diagnostic-not-found' };
2805
+ }
2806
+ if (record.state !== 'active') {
2807
+ return { ok: false, error: 'transport-diagnostic-not-active' };
2808
+ }
2809
+ if (record.operationInFlight === true) {
2810
+ return { ok: false, error: 'transport-diagnostic-operation-in-flight' };
2811
+ }
2812
+ if (!TRANSPORT_DIAGNOSTIC_PHASES.includes(normalizedPhase)) {
2813
+ return { ok: false, error: 'transport-diagnostic-phase-invalid' };
2814
+ }
2815
+ const currentIndex = TRANSPORT_DIAGNOSTIC_PHASES.indexOf(record.phase);
2816
+ const nextIndex = TRANSPORT_DIAGNOSTIC_PHASES.indexOf(normalizedPhase);
2817
+ const currentObserved = record.frames.some(frame => frame.phase === record.phase && frame.accepted === true);
2818
+ if (nextIndex !== currentIndex + 1 || !currentObserved) {
2819
+ return { ok: false, error: 'transport-diagnostic-phase-order-required' };
2820
+ }
2821
+ record.operationInFlight = true;
2822
+ try {
2823
+ const updated = await sendTransportDiagnosticCommand(record, 'set-phase', normalizedPhase);
2824
+ if (!updated.ok) {
2825
+ record.error = updated.error;
2826
+ record.updatedAt = new Date().toISOString();
2827
+ return { ok: false, error: updated.error };
2828
+ }
2829
+ if (record.state !== 'active') {
2830
+ return { ok: false, error: 'transport-diagnostic-interrupted' };
2831
+ }
2832
+ record.phase = normalizedPhase;
2833
+ record.phaseStartedAt = new Date().toISOString();
2834
+ record.updatedAt = record.phaseStartedAt;
2835
+ return serializeTransportDiagnostic(record);
2836
+ } finally {
2837
+ record.operationInFlight = false;
2838
+ }
2839
+ }
2840
+
2841
+ async function stopTransportDiagnostic(leaseId, token, reason = 'runner-complete') {
2842
+ const record = transportDiagnostics.get(safeString(leaseId, 128));
2843
+ if (!record || !diagnosticSecretEquals(record.token, token)) {
2844
+ return { ok: false, error: 'transport-diagnostic-not-found' };
2845
+ }
2846
+ if (record.state !== 'active') return serializeTransportDiagnostic(record);
2847
+ if (record.operationInFlight === true) {
2848
+ return { ok: false, error: 'transport-diagnostic-operation-in-flight' };
2849
+ }
2850
+ clearTimeout(record.expiryTimer);
2851
+ record.operationInFlight = true;
2852
+ try {
2853
+ appendTransportDiagnosticCleanup(record, 'release-requested', reason);
2854
+ const released = await sendTransportDiagnosticCommand(record, 'release');
2855
+ record.state = released.ok ? 'completed' : 'failed';
2856
+ record.error = released.ok ? '' : released.error;
2857
+ record.completedAt = new Date().toISOString();
2858
+ appendTransportDiagnosticCleanup(
2859
+ record,
2860
+ released.ok ? 'agent-released' : 'agent-release-unconfirmed',
2861
+ released.error);
2862
+ const serialized = serializeTransportDiagnostic(record);
2863
+ scheduleTransportDiagnosticRemoval(record);
2864
+ return serialized;
2865
+ } finally {
2866
+ record.operationInFlight = false;
2867
+ }
2868
+ }
2869
+
2870
+ function recordTransportDiagnosticFrame(device, message, payload, frame) {
2871
+ if (transportDiagnostics.size === 0 || message.transportDiagnosticApplied !== true) {
2872
+ return;
2873
+ }
2874
+ const leaseId = safeString(message.transportDiagnosticLeaseId, 128);
2875
+ const phase = safeString(message.transportDiagnosticPhase, 32).toLowerCase();
2876
+ if (!leaseId) return;
2877
+ const record = transportDiagnostics.get(leaseId);
2878
+ if (!record
2879
+ || record.state !== 'active'
2880
+ || record.phase !== phase
2881
+ || !transportDiagnosticOwnerMatches(record, device)
2882
+ || frame.streamId !== record.owner.streamId
2883
+ || frame.commandId !== record.owner.streamCommandId
2884
+ || frame.captureGeneration !== record.owner.captureGeneration
2885
+ || frame.monitorIndex !== record.owner.monitorIndex
2886
+ || safeString(frame.mimeType, 80).toLowerCase() !== 'video/h264') {
2887
+ return;
2888
+ }
2889
+ const expectedPath = expectedTransportForDiagnosticPhase(phase);
2890
+ const h264 = inspectAnnexBH264(payload);
2891
+ const agentPath = safeString(frame.frameTransportActive, 20).toLowerCase();
2892
+ const hubObservedPath = observedTransportPath(frame);
2893
+ const frameSeq = optionalFiniteNumber(frame.frameSeq);
2894
+ const switchCount = optionalFiniteNumber(message.frameTransportSwitchCount);
2895
+ const lastAcceptedFrame = [...record.frames]
2896
+ .reverse()
2897
+ .find(item => item.accepted === true);
2898
+ const previousFrameSeq = optionalFiniteNumber(
2899
+ lastAcceptedFrame?.frameSeq ?? record.baseline?.frameSeq);
2900
+ const previousSwitchCount = optionalFiniteNumber(
2901
+ lastAcceptedFrame?.switchCount ?? record.baseline?.switchCount);
2902
+ const sequenceAdvanced = frameSeq !== null
2903
+ && (previousFrameSeq === null || frameSeq > previousFrameSeq);
2904
+ const switchAdvanced = switchCount !== null
2905
+ && (lastAcceptedFrame
2906
+ ? previousSwitchCount !== null && switchCount > previousSwitchCount
2907
+ : previousSwitchCount === null || switchCount >= previousSwitchCount);
2908
+ const agentQueueDepth = optionalFiniteNumber(message.nativeOutputQueueDepth);
2909
+ const agentQueueBytes = optionalFiniteNumber(message.nativeOutputQueueBytes);
2910
+ const agentQueueDropped = optionalFiniteNumber(message.nativeOutputQueueDroppedCount);
2911
+ const hubIngressDropped = optionalFiniteNumber(message.hubIngressDropped);
2912
+ const udpIncompleteFrames = optionalFiniteNumber(message.udpIncompleteFrames);
2913
+ const hubIngressCounterEpochHash = transportDiagnosticIdentifierHash(
2914
+ message.hubIngressCounterEpoch,
2915
+ record.leaseId);
2916
+ const agentQueueTelemetryAvailable = agentQueueDepth !== null
2917
+ && agentQueueBytes !== null
2918
+ && agentQueueDropped !== null;
2919
+ const hubIngressTelemetryAvailable = hubIngressDropped !== null
2920
+ && !!hubIngressCounterEpochHash;
2921
+ const udpTelemetryAvailable = phase !== 'p2p' || udpIncompleteFrames !== null;
2922
+ const phaseLatencyMs = Math.max(
2923
+ 0,
2924
+ Date.parse(frame.receivedAt || '') - Date.parse(record.phaseStartedAt || ''));
2925
+ const accepted = agentPath === expectedPath
2926
+ && hubObservedPath === expectedPath
2927
+ && frame.isKeyFrame === true
2928
+ && h264.annexB
2929
+ && h264.hasIdr
2930
+ && h264.hasSps
2931
+ && h264.hasPps
2932
+ && h264.nalOrderValid
2933
+ && sequenceAdvanced
2934
+ && switchAdvanced
2935
+ && agentQueueTelemetryAvailable
2936
+ && hubIngressTelemetryAvailable
2937
+ && udpTelemetryAvailable
2938
+ && Number.isFinite(phaseLatencyMs);
2939
+ if (record.frames.some(item => item.phase === phase && item.accepted === true)) return;
2940
+ const observation = {
2941
+ phase,
2942
+ expectedPath,
2943
+ actualPath: agentPath,
2944
+ hubObservedPath,
2945
+ protocol: safeString(frame.frameTransportProtocol, 20),
2946
+ transport: safeString(frame.transport, 32),
2947
+ reason: safeString(frame.frameTransportReason, 160),
2948
+ receivedAt: frame.receivedAt,
2949
+ frameSeq,
2950
+ byteLength: frame.byteLength,
2951
+ contentHash: safeString(frame.contentHash, 128),
2952
+ isKeyFrame: frame.isKeyFrame === true,
2953
+ annexB: h264.annexB,
2954
+ hasIdr: h264.hasIdr,
2955
+ hasSps: h264.hasSps,
2956
+ hasPps: h264.hasPps,
2957
+ nalOrderValid: h264.nalOrderValid,
2958
+ switchCount,
2959
+ tcpFailureCount: frame.frameTransportTcpFailureCount,
2960
+ phaseLatencyMs,
2961
+ sequenceAdvanced,
2962
+ switchAdvanced,
2963
+ agentQueueDepth,
2964
+ agentQueueBytes,
2965
+ agentQueueDropped,
2966
+ hubIngressDropped,
2967
+ udpIncompleteFrames,
2968
+ hubIngressCounterEpochHash,
2969
+ agentQueueTelemetryAvailable,
2970
+ hubIngressTelemetryAvailable,
2971
+ udpTelemetryAvailable,
2972
+ accepted
2973
+ };
2974
+ if (accepted) {
2975
+ record.frames = record.frames.filter(item => item.phase !== phase);
2976
+ record.frames.push(observation);
2977
+ } else {
2978
+ const rejectedIndex = record.frames.findIndex(item =>
2979
+ item.phase === phase && item.accepted !== true);
2980
+ if (rejectedIndex >= 0) record.frames[rejectedIndex] = observation;
2981
+ else record.frames.push(observation);
2982
+ }
2983
+ if (record.frames.length > TRANSPORT_DIAGNOSTIC_PHASES.length * 2) {
2984
+ record.frames = record.frames.filter(item => item.accepted === true)
2985
+ .concat(record.frames.filter(item => item.accepted !== true)
2986
+ .slice(-TRANSPORT_DIAGNOSTIC_PHASES.length));
2987
+ }
2988
+ record.updatedAt = new Date().toISOString();
2989
+ }
2990
+
2269
2991
  function makeSyntheticFrame(device, streamId, mode = 'thumbnail', options = {}) {
2270
2992
  const now = new Date().toISOString();
2271
2993
  const descriptor = buildRemoteFrameTransferDescriptor(mode, mode === 'thumbnail' ? 'thumbnail' : DEFAULT_REMOTE_FRAME_MODE);
@@ -3135,9 +3857,10 @@ export function createRemoteHub(options = {}) {
3135
3857
  return;
3136
3858
  }
3137
3859
 
3138
- device.connected = false;
3139
- device.socket = null;
3140
- udpTransport?.unregisterDevice?.(deviceId, device.sessionId);
3860
+ device.connected = false;
3861
+ device.socket = null;
3862
+ abortTransportDiagnosticsForDevice(device, reason);
3863
+ udpTransport?.unregisterDevice?.(deviceId, device.sessionId);
3141
3864
  closeInputSocket(device, reason);
3142
3865
  closeFrameSocket(device, reason);
3143
3866
  device.disconnectedAt = new Date().toISOString();
@@ -3578,6 +4301,48 @@ export function createRemoteHub(options = {}) {
3578
4301
  ?? result.HubForwardedAtEpochMs
3579
4302
  ?? pending?.hubForwardedAtEpochMs
3580
4303
  ?? 0) || 0,
4304
+ duplicate: message.duplicate === true
4305
+ || message.Duplicate === true
4306
+ || result.duplicate === true
4307
+ || result.Duplicate === true,
4308
+ nativeInputBackend: safeString(
4309
+ message.nativeInputBackend
4310
+ || message.NativeInputBackend
4311
+ || result.nativeInputBackend
4312
+ || result.NativeInputBackend,
4313
+ 80),
4314
+ nativeEventsRequested: Math.max(0, Number(
4315
+ message.nativeEventsRequested
4316
+ ?? message.NativeEventsRequested
4317
+ ?? result.nativeEventsRequested
4318
+ ?? result.NativeEventsRequested
4319
+ ?? 0) || 0),
4320
+ nativeEventsAccepted: Math.max(0, Number(
4321
+ message.nativeEventsAccepted
4322
+ ?? message.NativeEventsAccepted
4323
+ ?? result.nativeEventsAccepted
4324
+ ?? result.NativeEventsAccepted
4325
+ ?? 0) || 0),
4326
+ hookEventsObserved: Math.max(0, Number(
4327
+ message.hookEventsObserved
4328
+ ?? message.HookEventsObserved
4329
+ ?? result.hookEventsObserved
4330
+ ?? result.HookEventsObserved
4331
+ ?? 0) || 0),
4332
+ hookEventsBlocked: Math.max(0, Number(
4333
+ message.hookEventsBlocked
4334
+ ?? message.HookEventsBlocked
4335
+ ?? result.hookEventsBlocked
4336
+ ?? result.HookEventsBlocked
4337
+ ?? 0) || 0),
4338
+ focusChanged: message.focusChanged === true
4339
+ || message.FocusChanged === true
4340
+ || result.focusChanged === true
4341
+ || result.FocusChanged === true,
4342
+ inputDiagnostic: message.inputDiagnostic === true
4343
+ || message.InputDiagnostic === true
4344
+ || result.inputDiagnostic === true
4345
+ || result.InputDiagnostic === true,
3581
4346
  fallback: pending?.fallback === true,
3582
4347
  hubAcknowledgedAtEpochMs: Date.now()
3583
4348
  };
@@ -4426,10 +5191,17 @@ export function createRemoteHub(options = {}) {
4426
5191
  nativeOutputQueueHighWatermark: Number.isFinite(Number(message.nativeOutputQueueHighWatermark)) ? Number(message.nativeOutputQueueHighWatermark) : 0,
4427
5192
  nativeOutputQueueDroppedCount: Number.isFinite(Number(message.nativeOutputQueueDroppedCount)) ? Number(message.nativeOutputQueueDroppedCount) : 0,
4428
5193
  nativeOutputQueueAwaitingKeyFrame: message.nativeOutputQueueAwaitingKeyFrame === true,
5194
+ nativeOutputQueueTelemetryAvailable:
5195
+ optionalFiniteNumber(message.nativeOutputQueueDepth) !== null
5196
+ && optionalFiniteNumber(message.nativeOutputQueueBytes) !== null
5197
+ && optionalFiniteNumber(message.nativeOutputQueueDroppedCount) !== null,
4429
5198
  nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
4430
5199
  droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
4431
5200
  hubIngressDropped: Number.isFinite(Number(message.hubIngressDropped)) ? Number(message.hubIngressDropped) : 0,
4432
5201
  udpIncompleteFrames: Number.isFinite(Number(message.udpIncompleteFrames)) ? Number(message.udpIncompleteFrames) : 0,
5202
+ hubIngressTelemetryAvailable: optionalFiniteNumber(message.hubIngressDropped) !== null,
5203
+ udpIncompleteTelemetryAvailable: optionalFiniteNumber(message.udpIncompleteFrames) !== null,
5204
+ hubIngressCounterEpoch: safeString(message.hubIngressCounterEpoch, 160),
4433
5205
  hardwareEncoder: safeString(message.hardwareEncoder, 80),
4434
5206
  platformProfile: safeString(message.platformProfile, 80),
4435
5207
  monitorIndex: activeMonitorIndex,
@@ -4480,9 +5252,10 @@ export function createRemoteHub(options = {}) {
4480
5252
  frameTransportTcpFailureCount: Number.isFinite(Number(message.frameTransportTcpFailureCount)) ? Number(message.frameTransportTcpFailureCount) : 0,
4481
5253
  sameContentStreak,
4482
5254
  payload,
4483
- accessToken: createFrameAccessToken()
4484
- };
4485
- streamState.latestFrame = device.latestLiveFrame;
5255
+ accessToken: createFrameAccessToken()
5256
+ };
5257
+ recordTransportDiagnosticFrame(device, message, payload, device.latestLiveFrame);
5258
+ streamState.latestFrame = device.latestLiveFrame;
4486
5259
  rememberRecentFramePayload(device, 'live', device.latestLiveFrame);
4487
5260
  emitFrame({
4488
5261
  kind: 'live',
@@ -4677,7 +5450,8 @@ export function createRemoteHub(options = {}) {
4677
5450
  if (frameKind === 'stream' || frameKind === 'live' || header.type === 'stream.binary') {
4678
5451
  applyLiveFrame(device, {
4679
5452
  ...header,
4680
- hubIngressDropped: Number(state.binaryQueueDrops || 0)
5453
+ hubIngressDropped: Number(state.binaryQueueDrops || 0),
5454
+ hubIngressCounterEpoch: state.binaryQueueEpoch
4681
5455
  }, framePayload, binaryTransport);
4682
5456
  return;
4683
5457
  }
@@ -4718,10 +5492,11 @@ export function createRemoteHub(options = {}) {
4718
5492
  lastFrameAt: new Date().toISOString(),
4719
5493
  framesReceived: Number(device.udp?.framesReceived || 0) + 1
4720
5494
  };
4721
- return applyLiveFrame(device, {
4722
- ...header,
4723
- transport: 'udp-p2p'
4724
- }, payload, 'udp-p2p');
5495
+ return applyLiveFrame(device, {
5496
+ ...header,
5497
+ transport: 'udp-p2p',
5498
+ hubIngressCounterEpoch: sessionId
5499
+ }, payload, 'udp-p2p');
4725
5500
  }
4726
5501
 
4727
5502
  function dropQueuedAgentBinaryFrame(state) {
@@ -5051,9 +5826,10 @@ export function createRemoteHub(options = {}) {
5051
5826
  binaryQueue: [],
5052
5827
  binaryQueueDraining: false,
5053
5828
  binaryQueueScheduled: false,
5054
- binaryQueueSocket: null,
5055
- binaryQueueDrops: 0,
5056
- closed: false
5829
+ binaryQueueSocket: null,
5830
+ binaryQueueDrops: 0,
5831
+ binaryQueueEpoch: crypto.randomUUID(),
5832
+ closed: false
5057
5833
  };
5058
5834
 
5059
5835
  const helloTimer = setTimeout(() => {
@@ -5211,9 +5987,10 @@ export function createRemoteHub(options = {}) {
5211
5987
  binaryQueue: [],
5212
5988
  binaryQueueDraining: false,
5213
5989
  binaryQueueScheduled: false,
5214
- binaryQueueSocket: null,
5215
- binaryQueueDrops: 0,
5216
- closed: false
5990
+ binaryQueueSocket: null,
5991
+ binaryQueueDrops: 0,
5992
+ binaryQueueEpoch: crypto.randomUUID(),
5993
+ closed: false
5217
5994
  };
5218
5995
 
5219
5996
  const helloTimer = setTimeout(() => {
@@ -5437,8 +6214,9 @@ export function createRemoteHub(options = {}) {
5437
6214
  return getStatus({ includeSecrets: false });
5438
6215
  }
5439
6216
 
5440
- async function close() {
6217
+ async function close() {
5441
6218
  for (const device of devices.values()) {
6219
+ abortTransportDiagnosticsForDevice(device, 'hub-shutdown');
5442
6220
  failAllPendingTasks(device, 'hub-shutdown');
5443
6221
  failAllPendingInputFallbacks(device, 'hub-shutdown');
5444
6222
  failPendingCommandResultWaiters(device, 'hub-shutdown');
@@ -5490,10 +6268,11 @@ export function createRemoteHub(options = {}) {
5490
6268
 
5491
6269
  function disconnectDevice(deviceId, reason = 'manager-disconnect') {
5492
6270
  const device = devices.get(String(deviceId || ''));
5493
- if (device?.synthetic === true) {
5494
- device.connected = false;
5495
- device.disconnectedAt = new Date().toISOString();
5496
- device.lastDisconnectReason = reason;
6271
+ if (device?.synthetic === true) {
6272
+ device.connected = false;
6273
+ device.disconnectedAt = new Date().toISOString();
6274
+ device.lastDisconnectReason = reason;
6275
+ abortTransportDiagnosticsForDevice(device, reason);
5497
6276
  deactivateDeviceLiveStreams(device, reason, device.disconnectedAt);
5498
6277
  failAllPendingTasks(device, 'device-disconnected');
5499
6278
  failAllPendingInputFallbacks(device, 'device-disconnected');
@@ -7547,6 +8326,10 @@ export function createRemoteHub(options = {}) {
7547
8326
  stopLiveStream,
7548
8327
  pauseLiveCapture,
7549
8328
  resumeLiveCapture,
8329
+ startTransportDiagnostic,
8330
+ getTransportDiagnostic,
8331
+ setTransportDiagnosticPhase,
8332
+ stopTransportDiagnostic,
7550
8333
  startAudioStream,
7551
8334
  stopAudioStream,
7552
8335
  getDeviceLiveFrame,