@livedesk/hub 0.1.8 → 0.1.10

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.8",
3
+ "version": "0.1.10",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
package/src/remote-hub.js CHANGED
@@ -29,7 +29,12 @@ const LIVE_STREAM_MIN_FRESH_MS = 3000;
29
29
  const LIVE_STREAM_MAX_FRESH_MS = 12000;
30
30
  const DEFAULT_REMOTE_INPUT_ACK_TIMEOUT_MS = 5000;
31
31
  const DEFAULT_LIVE_CAPTURE_STOP_ACK_TIMEOUT_MS = 8000;
32
- const REMOTE_PROTOCOL_VERSION = 2;
32
+ const REMOTE_POLICY_BYPASS_COMMANDS = new Set([
33
+ 'ping',
34
+ 'stream.stop',
35
+ 'audio.stop'
36
+ ]);
37
+ const REMOTE_PROTOCOL_VERSION = 2;
33
38
  const REMOTE_AGENT_PROTOCOL = 'mindexec.remote.agent';
34
39
  const REMOTE_FRAME_PROTOCOL = 'mindexec.remote.frame.v2';
35
40
  const REMOTE_FRAME_PROTOCOL_VERSION = 2;
@@ -153,12 +158,14 @@ const DEFAULT_PUBLIC_IP_TIMEOUT_MS = 1800;
153
158
  const DUPLICATE_DEVICE_ACTIVE_REJECT_MS = 15000;
154
159
  const DUPLICATE_DEVICE_LOG_THROTTLE_MS = 60000;
155
160
  const DUPLICATE_DEVICE_RETRY_AFTER_MS = 30000;
156
- const AGENT_BINARY_INGRESS_QUEUE_PACKETS = 8;
157
- const MODE5_AGENT_BINARY_INGRESS_QUEUE_PACKETS = 2;
158
- // Native capture backends can emit short bursts (ScreenCaptureKit commonly
159
- // queues up to eight frames). Drain one frame per event-loop turn so a burst
160
- // cannot immediately overflow the smaller Hub-to-browser send lanes.
161
- const AGENT_BINARY_INGRESS_DRAIN_BUDGET_PACKETS = 1;
161
+ const AGENT_BINARY_INGRESS_QUEUE_PACKETS = 16;
162
+ const MODE5_AGENT_BINARY_INGRESS_QUEUE_PACKETS = 2;
163
+ // Native capture backends can emit short bursts (ScreenCaptureKit plus the
164
+ // Agent handoff can hold two eight-frame batches). Drain the complete bounded
165
+ // ingress lane in one event-loop turn. The Hub-to-browser lanes remain
166
+ // independently bounded and own browser backpressure; throttling ingress one
167
+ // packet per turn only drops dependent H.264 deltas before those lanes.
168
+ const AGENT_BINARY_INGRESS_DRAIN_BUDGET_PACKETS = 64;
162
169
  const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
163
170
  const SYNTHETIC_FRAME_PAYLOAD = Buffer.from(SYNTHETIC_FRAME_DATA_URL.split(',')[1], 'base64');
164
171
  const SYNTHETIC_FRAME_HASH = crypto.createHash('sha256').update(SYNTHETIC_FRAME_PAYLOAD).digest('hex').slice(0, 16);
@@ -1515,12 +1522,16 @@ export function createRemoteHub(options = {}) {
1515
1522
  }
1516
1523
  }
1517
1524
 
1518
- function policyError(device, permission = '') {
1519
- const policy = getDevicePolicy(device);
1520
- if (policy.accessMode === 'block-remote-access') return 'remote-access-blocked-by-settings';
1521
- if (permission && policy[permission] === false) {
1522
- return `${permission}-blocked-by-settings`;
1523
- }
1525
+ function policyError(device, permission = '', command = '') {
1526
+ const policy = getDevicePolicy(device);
1527
+ const commandName = safeString(command, 80);
1528
+ if (policy.accessMode === 'block-remote-access'
1529
+ && !REMOTE_POLICY_BYPASS_COMMANDS.has(commandName)) {
1530
+ return 'remote-access-blocked-by-settings';
1531
+ }
1532
+ if (permission && policy[permission] === false) {
1533
+ return `${permission}-blocked-by-settings`;
1534
+ }
1524
1535
  return '';
1525
1536
  }
1526
1537
 
@@ -3855,10 +3866,23 @@ export function createRemoteHub(options = {}) {
3855
3866
  agentSendGapMs: Number.isFinite(Number(message.agentSendGapMs)) ? Number(message.agentSendGapMs) : 0,
3856
3867
  agentSendDurationMs: Number.isFinite(Number(message.agentSendDurationMs)) ? Number(message.agentSendDurationMs) : 0,
3857
3868
  agentSendDurationFrameSeq: Number.isFinite(Number(message.agentSendDurationFrameSeq)) ? Number(message.agentSendDurationFrameSeq) : 0,
3858
- agentPaceMs: Number.isFinite(Number(message.agentPaceMs)) ? Number(message.agentPaceMs) : 0,
3859
- agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
3860
- agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
3861
- droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
3869
+ agentPaceMs: Number.isFinite(Number(message.agentPaceMs)) ? Number(message.agentPaceMs) : 0,
3870
+ agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
3871
+ agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
3872
+ nativeCaptureTelemetryAvailable: message.nativeCaptureTelemetryAvailable === true,
3873
+ nativeEncoderHardwareAccelerationKnown: message.nativeEncoderHardwareAccelerationKnown === true,
3874
+ nativeEncoderHardwareAccelerated: typeof message.nativeEncoderHardwareAccelerated === 'boolean'
3875
+ ? message.nativeEncoderHardwareAccelerated
3876
+ : null,
3877
+ nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
3878
+ nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
3879
+ nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
3880
+ nativeCaptureSampleCount: Number.isFinite(Number(message.nativeCaptureSampleCount)) ? Number(message.nativeCaptureSampleCount) : 0,
3881
+ nativeEncodableFrameCount: Number.isFinite(Number(message.nativeEncodableFrameCount)) ? Number(message.nativeEncodableFrameCount) : 0,
3882
+ nativeSkippedFrameCount: Number.isFinite(Number(message.nativeSkippedFrameCount)) ? Number(message.nativeSkippedFrameCount) : 0,
3883
+ nativeEncodedFrameCount: Number.isFinite(Number(message.nativeEncodedFrameCount)) ? Number(message.nativeEncodedFrameCount) : 0,
3884
+ nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
3885
+ droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
3862
3886
  hardwareEncoder: safeString(message.hardwareEncoder, 80),
3863
3887
  platformProfile: safeString(message.platformProfile, 80),
3864
3888
  transferProtocol: transfer.transferProtocol,
@@ -4219,9 +4243,22 @@ export function createRemoteHub(options = {}) {
4219
4243
  agentSendGapMs: Number.isFinite(Number(message.agentSendGapMs)) ? Number(message.agentSendGapMs) : 0,
4220
4244
  agentSendDurationMs: Number.isFinite(Number(message.agentSendDurationMs)) ? Number(message.agentSendDurationMs) : 0,
4221
4245
  agentSendDurationFrameSeq: Number.isFinite(Number(message.agentSendDurationFrameSeq)) ? Number(message.agentSendDurationFrameSeq) : 0,
4222
- agentPaceMs: Number.isFinite(Number(message.agentPaceMs)) ? Number(message.agentPaceMs) : 0,
4223
- agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
4224
- agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
4246
+ agentPaceMs: Number.isFinite(Number(message.agentPaceMs)) ? Number(message.agentPaceMs) : 0,
4247
+ agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
4248
+ agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
4249
+ nativeCaptureTelemetryAvailable: message.nativeCaptureTelemetryAvailable === true,
4250
+ nativeEncoderHardwareAccelerationKnown: message.nativeEncoderHardwareAccelerationKnown === true,
4251
+ nativeEncoderHardwareAccelerated: typeof message.nativeEncoderHardwareAccelerated === 'boolean'
4252
+ ? message.nativeEncoderHardwareAccelerated
4253
+ : null,
4254
+ nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
4255
+ nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
4256
+ nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
4257
+ nativeCaptureSampleCount: Number.isFinite(Number(message.nativeCaptureSampleCount)) ? Number(message.nativeCaptureSampleCount) : 0,
4258
+ nativeEncodableFrameCount: Number.isFinite(Number(message.nativeEncodableFrameCount)) ? Number(message.nativeEncodableFrameCount) : 0,
4259
+ nativeSkippedFrameCount: Number.isFinite(Number(message.nativeSkippedFrameCount)) ? Number(message.nativeSkippedFrameCount) : 0,
4260
+ nativeEncodedFrameCount: Number.isFinite(Number(message.nativeEncodedFrameCount)) ? Number(message.nativeEncodedFrameCount) : 0,
4261
+ nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
4225
4262
  droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
4226
4263
  hubIngressDropped: Number.isFinite(Number(message.hubIngressDropped)) ? Number(message.hubIngressDropped) : 0,
4227
4264
  udpIncompleteFrames: Number.isFinite(Number(message.udpIncompleteFrames)) ? Number(message.udpIncompleteFrames) : 0,
@@ -5348,7 +5385,7 @@ export function createRemoteHub(options = {}) {
5348
5385
  : commandName.startsWith('agent.')
5349
5386
  ? 'allowAgent'
5350
5387
  : '';
5351
- const denied = policyError(device, requiredPermission);
5388
+ const denied = policyError(device, requiredPermission, commandName);
5352
5389
  if (denied) return { ok: false, error: denied };
5353
5390
  if (device?.synthetic === true && device.connected) {
5354
5391
  const commandId = safeString(command?.commandId, 128) || crypto.randomUUID();
@@ -6899,7 +6936,7 @@ export function createRemoteHub(options = {}) {
6899
6936
  if (!device) {
6900
6937
  return { ok: false, paused: false, error: 'device-not-found' };
6901
6938
  }
6902
- const denied = policyError(device);
6939
+ const denied = policyError(device, '', 'stream.stop');
6903
6940
  if (denied) return { ok: false, paused: false, error: denied };
6904
6941
 
6905
6942
  let pause = device.liveCapturePause;
package/src/server.js CHANGED
@@ -61,7 +61,11 @@ const runtimeManager = createHubRuntime({
61
61
  });
62
62
  runtimeManager.emit('runtime.started', { role: runtimeRole, pid: process.pid });
63
63
  const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 8 * 1024 * 1024);
64
- const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 8);
64
+ // ScreenCaptureKit and the native Agent handoff can release two bounded
65
+ // eight-frame batches together. Keep the Wall lane large enough to retain the
66
+ // complete dependent H.264 burst; Control keeps its separate two-frame
67
+ // low-latency lane below.
68
+ const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 16);
65
69
  const controlFrameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_CONTROL_FRAME_WS_QUEUE_PACKETS', 2);
66
70
  const frameStreamStopGraceMs = readPositiveIntegerEnv('LIVEDESK_FRAME_STREAM_STOP_GRACE_MS', 1500);
67
71
  const mode5FrameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_MODE5_WS_BACKPRESSURE_BYTES', 2 * 1024 * 1024);
@@ -2050,9 +2054,22 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
2050
2054
  agentSendGapMs: Number(frame.agentSendGapMs || 0) || 0,
2051
2055
  agentSendDurationMs: Number(frame.agentSendDurationMs || 0) || 0,
2052
2056
  agentSendDurationFrameSeq: Number(frame.agentSendDurationFrameSeq || 0) || 0,
2053
- agentPaceMs: Number(frame.agentPaceMs || 0) || 0,
2054
- agentQueueBeforePaceMs: Number(frame.agentQueueBeforePaceMs || 0) || 0,
2055
- agentFrameAgeMs: Number(frame.agentFrameAgeMs || 0) || 0,
2057
+ agentPaceMs: Number(frame.agentPaceMs || 0) || 0,
2058
+ agentQueueBeforePaceMs: Number(frame.agentQueueBeforePaceMs || 0) || 0,
2059
+ agentFrameAgeMs: Number(frame.agentFrameAgeMs || 0) || 0,
2060
+ nativeCaptureTelemetryAvailable: frame.nativeCaptureTelemetryAvailable === true,
2061
+ nativeEncoderHardwareAccelerationKnown: frame.nativeEncoderHardwareAccelerationKnown === true,
2062
+ nativeEncoderHardwareAccelerated: typeof frame.nativeEncoderHardwareAccelerated === 'boolean'
2063
+ ? frame.nativeEncoderHardwareAccelerated
2064
+ : null,
2065
+ nativeEncoderHardwareQueryStatus: frame.nativeEncoderHardwareQueryStatus || '',
2066
+ nativeCaptureFps: Number(frame.nativeCaptureFps || 0) || 0,
2067
+ nativeEncodedFps: Number(frame.nativeEncodedFps || 0) || 0,
2068
+ nativeCaptureSampleCount: Number(frame.nativeCaptureSampleCount || 0) || 0,
2069
+ nativeEncodableFrameCount: Number(frame.nativeEncodableFrameCount || 0) || 0,
2070
+ nativeSkippedFrameCount: Number(frame.nativeSkippedFrameCount || 0) || 0,
2071
+ nativeEncodedFrameCount: Number(frame.nativeEncodedFrameCount || 0) || 0,
2072
+ nativeCaptureTelemetryAt: frame.nativeCaptureTelemetryAt || '',
2056
2073
  droppedByAgent: Number(frame.droppedByAgent || 0) || 0,
2057
2074
  hubIngressDropped: Number(frame.hubIngressDropped || 0) || 0,
2058
2075
  hubClientDropped: Number(diagnostics.hubClientDropped || 0) || 0,
@@ -4071,7 +4088,7 @@ app.post('/api/remote/devices/:deviceId/live/stop', (req, res) => {
4071
4088
  res.json(remoteHub.stopLiveStream(req.params.deviceId, req.body || {}));
4072
4089
  });
4073
4090
 
4074
- app.post('/api/remote/devices/:deviceId/live/pause', requireHubFeatureAccess, async (req, res, next) => {
4091
+ app.post('/api/remote/devices/:deviceId/live/pause', async (req, res, next) => {
4075
4092
  noStore(res);
4076
4093
  try {
4077
4094
  res.json(await remoteHub.pauseLiveCapture(req.params.deviceId, req.body || {}));