livedesk 0.1.456 → 0.1.457

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/hub/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -0,0 +1,38 @@
1
+ export const LIVE_STREAM_MONITOR_INDEX_MIN = 0;
2
+ export const LIVE_STREAM_MONITOR_INDEX_MAX = 63;
3
+
4
+ export function parseExactLiveStreamMonitorIndex(value) {
5
+ return typeof value === 'number'
6
+ && Number.isInteger(value)
7
+ && value >= LIVE_STREAM_MONITOR_INDEX_MIN
8
+ && value <= LIVE_STREAM_MONITOR_INDEX_MAX
9
+ ? value
10
+ : null;
11
+ }
12
+
13
+ export function isReusedLiveStreamFrameReady(result, activeStream, expectedBinding) {
14
+ const expectedMonitorIndex = parseExactLiveStreamMonitorIndex(expectedBinding?.monitorIndex);
15
+ const activeMonitorIndex = parseExactLiveStreamMonitorIndex(activeStream?.monitorIndex);
16
+ const latestFrame = activeStream?.latestFrame;
17
+ const frameMonitorIndex = parseExactLiveStreamMonitorIndex(latestFrame?.monitorIndex);
18
+ const expectedStreamId = String(expectedBinding?.streamId || '');
19
+ const expectedCommandId = String(expectedBinding?.commandId || '');
20
+ const expectedCaptureGeneration = Number(expectedBinding?.captureGeneration || 0);
21
+
22
+ return result?.reused === true
23
+ && result?.ready === true
24
+ && Number(activeStream?.framesReceived || 0) > 0
25
+ && !!expectedStreamId
26
+ && String(activeStream?.streamId || '') === expectedStreamId
27
+ && !!expectedCommandId
28
+ && String(activeStream?.commandId || '') === expectedCommandId
29
+ && Number.isInteger(expectedCaptureGeneration)
30
+ && expectedCaptureGeneration > 0
31
+ && Number(activeStream?.captureGeneration || 0) === expectedCaptureGeneration
32
+ && latestFrame?.currentGenerationVerified === true
33
+ && String(latestFrame?.commandId || '') === expectedCommandId
34
+ && Number(latestFrame?.captureGeneration || 0) === expectedCaptureGeneration
35
+ && expectedMonitorIndex !== null
36
+ && activeMonitorIndex === expectedMonitorIndex
37
+ && frameMonitorIndex === expectedMonitorIndex;
38
+ }
@@ -2,6 +2,7 @@ import net from 'net';
2
2
  import os from 'os';
3
3
  import crypto from 'crypto';
4
4
  import { createHubRelayControl } from './transport/relay-hub-control.js';
5
+ import { parseExactLiveStreamMonitorIndex } from './live-stream-monitor-contract.js';
5
6
 
6
7
  const DEFAULT_REMOTE_HUB_PORT = 5197;
7
8
  const DEFAULT_REMOTE_HUB_HOST = '0.0.0.0';
@@ -3983,6 +3984,34 @@ export function createRemoteHub(options = {}) {
3983
3984
  });
3984
3985
  return null;
3985
3986
  }
3987
+ const pendingMonitorIndex = parseExactLiveStreamMonitorIndex(pending.monitorIndex);
3988
+ const messageMonitorIndex = parseExactLiveStreamMonitorIndex(message.monitorIndex);
3989
+ if (pendingMonitorIndex === null || messageMonitorIndex === null) {
3990
+ emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
3991
+ reason: 'invalid-pending-monitor-metadata',
3992
+ streamId: streamState.streamId,
3993
+ commandId,
3994
+ captureGeneration: messageCaptureGeneration,
3995
+ pendingCaptureGeneration,
3996
+ monitorIndex: messageMonitorIndex,
3997
+ pendingMonitorIndex,
3998
+ transport
3999
+ });
4000
+ return null;
4001
+ }
4002
+ if (messageMonitorIndex !== pendingMonitorIndex) {
4003
+ emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
4004
+ reason: 'stale-pending-monitor',
4005
+ streamId: streamState.streamId,
4006
+ commandId,
4007
+ captureGeneration: messageCaptureGeneration,
4008
+ pendingCaptureGeneration,
4009
+ monitorIndex: messageMonitorIndex,
4010
+ pendingMonitorIndex,
4011
+ transport
4012
+ });
4013
+ return null;
4014
+ }
3986
4015
 
3987
4016
  const previousCommandId = safeString(streamState.commandId, 128);
3988
4017
  const transfer = buildRemoteFrameTransferDescriptorFromMessage(
@@ -4011,9 +4040,7 @@ export function createRemoteHub(options = {}) {
4011
4040
  height: Number.isFinite(Number(message.height)) ? Number(message.height) : Number(pending.height || 0),
4012
4041
  sourceWidth: Number.isFinite(Number(message.sourceWidth)) ? Number(message.sourceWidth) : Number(pending.sourceWidth || 0),
4013
4042
  sourceHeight: Number.isFinite(Number(message.sourceHeight)) ? Number(message.sourceHeight) : Number(pending.sourceHeight || 0),
4014
- monitorIndex: Number.isFinite(Number(message.monitorIndex))
4015
- ? normalizeMonitorIndex(message.monitorIndex)
4016
- : Number(pending.monitorIndex || 0),
4043
+ monitorIndex: pendingMonitorIndex,
4017
4044
  monitorCount: Number.isFinite(Number(message.monitorCount))
4018
4045
  ? clampNumber(message.monitorCount, 1, 64, 1)
4019
4046
  : Number(pending.monitorCount || 1),
@@ -4106,6 +4133,37 @@ export function createRemoteHub(options = {}) {
4106
4133
  return true;
4107
4134
  }
4108
4135
 
4136
+ const activeMonitorIndex = parseExactLiveStreamMonitorIndex(streamState.monitorIndex);
4137
+ const messageMonitorIndex = parseExactLiveStreamMonitorIndex(message.monitorIndex);
4138
+ if (activeMonitorIndex === null || messageMonitorIndex === null) {
4139
+ emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
4140
+ reason: 'invalid-live-stream-monitor-metadata',
4141
+ streamId,
4142
+ commandId,
4143
+ activeCommandId,
4144
+ captureGeneration: Number(message.captureGeneration || 0),
4145
+ activeCaptureGeneration: Number(streamState.captureGeneration || 0),
4146
+ monitorIndex: messageMonitorIndex,
4147
+ activeMonitorIndex,
4148
+ transport
4149
+ });
4150
+ return false;
4151
+ }
4152
+ if (messageMonitorIndex !== activeMonitorIndex) {
4153
+ emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
4154
+ reason: 'stale-live-stream-monitor',
4155
+ streamId,
4156
+ commandId,
4157
+ activeCommandId,
4158
+ captureGeneration: Number(message.captureGeneration || 0),
4159
+ activeCaptureGeneration: Number(streamState.captureGeneration || 0),
4160
+ monitorIndex: messageMonitorIndex,
4161
+ activeMonitorIndex,
4162
+ transport
4163
+ });
4164
+ return false;
4165
+ }
4166
+
4109
4167
  const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
4110
4168
  const openedAt = safeString(message.openedAt, 80) || device.lastSeenAt || new Date().toISOString();
4111
4169
  const nextStreamState = {
@@ -4126,7 +4184,7 @@ export function createRemoteHub(options = {}) {
4126
4184
  height: Number.isFinite(Number(message.height)) ? Number(message.height) : Number(streamState.height || 0),
4127
4185
  sourceWidth: Number.isFinite(Number(message.sourceWidth)) ? Number(message.sourceWidth) : Number(streamState.sourceWidth || 0),
4128
4186
  sourceHeight: Number.isFinite(Number(message.sourceHeight)) ? Number(message.sourceHeight) : Number(streamState.sourceHeight || 0),
4129
- monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(streamState.monitorIndex || 0),
4187
+ monitorIndex: activeMonitorIndex,
4130
4188
  monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(streamState.monitorCount || 1),
4131
4189
  streamPurpose: safeString(message.streamPurpose || streamState.streamPurpose, 24) || 'wall',
4132
4190
  captureGeneration: Number.isSafeInteger(Number(message.captureGeneration)) && Number(message.captureGeneration) > 0
@@ -4204,8 +4262,40 @@ export function createRemoteHub(options = {}) {
4204
4262
  });
4205
4263
  return false;
4206
4264
  }
4207
-
4208
- const byteLength = normalizeFrameByteLength(framePayload, frameData);
4265
+ const activeMonitorIndex = parseExactLiveStreamMonitorIndex(streamState.monitorIndex);
4266
+ const frameMonitorIndex = parseExactLiveStreamMonitorIndex(message.monitorIndex);
4267
+ if (activeMonitorIndex === null || frameMonitorIndex === null) {
4268
+ device.counters.liveFramesDropped += 1;
4269
+ emitRemoteEvent('RemoteFrameDropped', device, {
4270
+ reason: 'invalid-live-stream-monitor-frame-metadata',
4271
+ streamId,
4272
+ commandId,
4273
+ captureGeneration: frameCaptureGeneration,
4274
+ activeCaptureGeneration,
4275
+ monitorIndex: frameMonitorIndex,
4276
+ activeMonitorIndex,
4277
+ frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
4278
+ transport
4279
+ });
4280
+ return false;
4281
+ }
4282
+ if (frameMonitorIndex !== activeMonitorIndex) {
4283
+ device.counters.liveFramesDropped += 1;
4284
+ emitRemoteEvent('RemoteFrameDropped', device, {
4285
+ reason: 'stale-live-stream-monitor-frame',
4286
+ streamId,
4287
+ commandId,
4288
+ captureGeneration: frameCaptureGeneration,
4289
+ activeCaptureGeneration,
4290
+ monitorIndex: frameMonitorIndex,
4291
+ activeMonitorIndex,
4292
+ frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
4293
+ transport
4294
+ });
4295
+ return false;
4296
+ }
4297
+
4298
+ const byteLength = normalizeFrameByteLength(framePayload, frameData);
4209
4299
  if ((!Buffer.isBuffer(framePayload) && !frameData)
4210
4300
  || byteLength > MAX_STREAM_BINARY_BYTES
4211
4301
  || !Number.isFinite(frameSeq)) {
@@ -4242,7 +4332,8 @@ export function createRemoteHub(options = {}) {
4242
4332
  const currentGenerationVerified = (!activeCommandId || commandId === activeCommandId)
4243
4333
  && (activeCaptureGeneration <= 0
4244
4334
  || (frameCaptureGeneration > 0
4245
- && frameCaptureGeneration === activeCaptureGeneration));
4335
+ && frameCaptureGeneration === activeCaptureGeneration))
4336
+ && frameMonitorIndex === activeMonitorIndex;
4246
4337
  const readyFrameReceivedBeforeFrame = streamState.readyFrameReceived === true;
4247
4338
  const captureTimingAvailable = hasFrameCaptureTiming(message);
4248
4339
  const videoTransport = transport === 'udp-p2p'
@@ -4323,7 +4414,7 @@ export function createRemoteHub(options = {}) {
4323
4414
  udpIncompleteFrames: Number.isFinite(Number(message.udpIncompleteFrames)) ? Number(message.udpIncompleteFrames) : 0,
4324
4415
  hardwareEncoder: safeString(message.hardwareEncoder, 80),
4325
4416
  platformProfile: safeString(message.platformProfile, 80),
4326
- monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(streamState.monitorIndex || 0),
4417
+ monitorIndex: activeMonitorIndex,
4327
4418
  monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(streamState.monitorCount || 1),
4328
4419
  transferProtocol: transfer.transferProtocol,
4329
4420
  transferProtocolVersion: transfer.transferProtocolVersion,
@@ -4402,7 +4493,6 @@ export function createRemoteHub(options = {}) {
4402
4493
  streamState.height = device.latestLiveFrame.height;
4403
4494
  streamState.sourceWidth = device.latestLiveFrame.sourceWidth;
4404
4495
  streamState.sourceHeight = device.latestLiveFrame.sourceHeight;
4405
- streamState.monitorIndex = device.latestLiveFrame.monitorIndex;
4406
4496
  streamState.monitorCount = device.latestLiveFrame.monitorCount;
4407
4497
  ensureDeviceLiveStreams(device).set(streamId, streamState);
4408
4498
  device.counters.liveFramesReceived += 1;
@@ -6457,8 +6547,12 @@ export function createRemoteHub(options = {}) {
6457
6547
  }
6458
6548
  const activeCaptureGeneration = Number(activeLiveStream.captureGeneration || 0);
6459
6549
  const frameCaptureGeneration = Number(latestFrame.captureGeneration || 0);
6460
- return activeCaptureGeneration <= 0
6461
- || (frameCaptureGeneration > 0 && frameCaptureGeneration === activeCaptureGeneration);
6550
+ const activeMonitorIndex = parseExactLiveStreamMonitorIndex(activeLiveStream.monitorIndex);
6551
+ const frameMonitorIndex = parseExactLiveStreamMonitorIndex(latestFrame.monitorIndex);
6552
+ return activeMonitorIndex !== null
6553
+ && frameMonitorIndex === activeMonitorIndex
6554
+ && (activeCaptureGeneration <= 0
6555
+ || (frameCaptureGeneration > 0 && frameCaptureGeneration === activeCaptureGeneration));
6462
6556
  }
6463
6557
 
6464
6558
  function getLiveStreamFreshWindowMs(activeLiveStream) {
package/hub/src/server.js CHANGED
@@ -7,8 +7,9 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, wri
7
7
  import { dirname, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import os from 'node:os';
10
- import { WebSocketServer } from 'ws';
10
+ import { WebSocketServer } from 'ws';
11
11
  import { createRemoteHub } from './remote-hub.js';
12
+ import { isReusedLiveStreamFrameReady } from './live-stream-monitor-contract.js';
12
13
  import { buildMode4AtlasSessionKey, Mode4AtlasPool, planMode4AtlasInputTransitions } from './mode4-atlas-pool.js';
13
14
  import { resolveMode4AtlasTileSize } from './mode4-atlas-sizing.js';
14
15
  import { createHubFilesystem } from './filesystem/hub-filesystem.js';
@@ -1976,12 +1977,7 @@ function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '',
1976
1977
  readySent: false
1977
1978
  };
1978
1979
  const activeStream = device?.activeLiveStream;
1979
- const reusedFrameReady = result.reused === true
1980
- && result.ready === true
1981
- && Number(result.framesReceived ?? activeStream?.framesReceived ?? 0) > 0
1982
- && String(activeStream?.streamId || expectedBinding.streamId) === expectedBinding.streamId
1983
- && String(activeStream?.commandId || expectedBinding.commandId) === expectedBinding.commandId
1984
- && Number(activeStream?.captureGeneration || expectedBinding.captureGeneration) === expectedBinding.captureGeneration;
1980
+ const reusedFrameReady = isReusedLiveStreamFrameReady(result, activeStream, expectedBinding);
1985
1981
  expectedBinding.readySent = reusedFrameReady;
1986
1982
  ws.liveDeskStreamIdsByDeviceId.set(deviceId, result.streamId);
1987
1983
  ws.liveDeskExpectedStreamBindingsByDeviceId.set(deviceId, expectedBinding);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.456",
3
+ "version": "0.1.457",
4
4
  "livedeskClientVersion": "0.1.211",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",