@livedesk/hub 0.1.37 → 0.1.39

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.37",
3
+ "version": "0.1.39",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -8,10 +8,11 @@ export const LIVE_DESK_UPDATE_COMMAND = 'livedesk.client-update';
8
8
  // same device returned, so the Hub-owned compatibility bridge upgrades them.
9
9
  export const LIVE_DESK_DEDICATED_CLIENT_UPDATE_MIN_VERSION = '0.1.172';
10
10
  export const LIVE_DESK_UPDATE_CLIENT_BATCH_SIZE = 5;
11
- export const LIVE_DESK_UPDATE_TARGET_TIMEOUT_MS = 480_000;
12
- export const LIVE_DESK_UPDATE_TIMEOUT_MS = 90 * 60_000;
13
- export const LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS = 60_000;
14
- export const LIVE_DESK_UPDATE_CHECK_INTERVAL_MS = 60_000;
11
+ export const LIVE_DESK_UPDATE_TARGET_TIMEOUT_MS = 480_000;
12
+ export const LIVE_DESK_UPDATE_TIMEOUT_MS = 90 * 60_000;
13
+ export const LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS = 60_000;
14
+ export const LIVE_DESK_UPDATE_VERIFIED_CLIENT_STABILITY_MS = 10_000;
15
+ export const LIVE_DESK_UPDATE_CHECK_INTERVAL_MS = 60_000;
15
16
  export const LIVE_DESK_LEGACY_COMMAND_MAX_LENGTH = 3900;
16
17
 
17
18
  function cleanVersion(value) {
@@ -194,9 +195,10 @@ function statusForRun(run) {
194
195
  activeCount,
195
196
  queuedCount,
196
197
  pendingOfflineCount,
197
- failedCount,
198
- batchSize: run.batchSize,
199
- clientRestartStabilityMs: run.clientRestartStabilityMs,
198
+ failedCount,
199
+ batchSize: run.batchSize,
200
+ clientRestartStabilityMs: run.clientRestartStabilityMs,
201
+ verifiedClientRestartStabilityMs: run.verifiedClientRestartStabilityMs,
200
202
  latestManagerVersion: run.latestManagerVersion,
201
203
  latestClientVersion: run.latestClientVersion,
202
204
  error: run.error || '',
@@ -226,10 +228,14 @@ function resetReconnectCandidate(target) {
226
228
  target.candidatePid = 0;
227
229
  target.candidateConnectedAt = '';
228
230
  target.candidateProductVersion = '';
229
- target.candidateAgentVersion = '';
230
- target.candidateSince = '';
231
- target.stabilityDeadlineAt = '';
232
- }
231
+ target.candidateAgentVersion = '';
232
+ target.candidateSupervisorProofId = '';
233
+ target.verificationSource = '';
234
+ target.requiredStabilityMs = 0;
235
+ target.supervisorProofAt = '';
236
+ target.candidateSince = '';
237
+ target.stabilityDeadlineAt = '';
238
+ }
233
239
 
234
240
  export function createLiveDeskUpdateManager({
235
241
  remoteHub,
@@ -240,10 +246,11 @@ export function createLiveDeskUpdateManager({
240
246
  fetchImpl = globalThis.fetch,
241
247
  now = () => Date.now(),
242
248
  clientBatchSize = LIVE_DESK_UPDATE_CLIENT_BATCH_SIZE,
243
- targetTimeoutMs = LIVE_DESK_UPDATE_TARGET_TIMEOUT_MS,
244
- operationTimeoutMs = LIVE_DESK_UPDATE_TIMEOUT_MS,
245
- clientRestartStabilityMs = LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS
246
- }) {
249
+ targetTimeoutMs = LIVE_DESK_UPDATE_TARGET_TIMEOUT_MS,
250
+ operationTimeoutMs = LIVE_DESK_UPDATE_TIMEOUT_MS,
251
+ clientRestartStabilityMs = LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS,
252
+ verifiedClientRestartStabilityMs = LIVE_DESK_UPDATE_VERIFIED_CLIENT_STABILITY_MS
253
+ }) {
247
254
  let latestRelease = null;
248
255
  let checkError = '';
249
256
  let checkPromise = null;
@@ -262,12 +269,18 @@ export function createLiveDeskUpdateManager({
262
269
  effectiveTargetTimeoutMs,
263
270
  Number(operationTimeoutMs) || LIVE_DESK_UPDATE_TIMEOUT_MS
264
271
  );
265
- const effectiveClientRestartStabilityMs = Math.max(
272
+ const effectiveClientRestartStabilityMs = Math.max(
266
273
  0,
267
274
  Number.isFinite(Number(clientRestartStabilityMs))
268
275
  ? Number(clientRestartStabilityMs)
269
- : LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS
270
- );
276
+ : LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS
277
+ );
278
+ const effectiveVerifiedClientRestartStabilityMs = Math.max(
279
+ 5_000,
280
+ Number.isFinite(Number(verifiedClientRestartStabilityMs))
281
+ ? Number(verifiedClientRestartStabilityMs)
282
+ : LIVE_DESK_UPDATE_VERIFIED_CLIENT_STABILITY_MS
283
+ );
271
284
 
272
285
  const touch = () => {
273
286
  if (run) run.updatedAt = new Date(now()).toISOString();
@@ -341,9 +354,10 @@ export function createLiveDeskUpdateManager({
341
354
  queuedCount: 0,
342
355
  pendingOfflineCount: 0,
343
356
  failedCount: 0,
344
- batchSize: effectiveClientBatchSize,
345
- clientRestartStabilityMs: effectiveClientRestartStabilityMs,
346
- targets: []
357
+ batchSize: effectiveClientBatchSize,
358
+ clientRestartStabilityMs: effectiveClientRestartStabilityMs,
359
+ verifiedClientRestartStabilityMs: effectiveVerifiedClientRestartStabilityMs,
360
+ targets: []
347
361
  })
348
362
  };
349
363
  };
@@ -535,9 +549,30 @@ export function createLiveDeskUpdateManager({
535
549
  const connectedAtEpochMs = Date.parse(connectedAt);
536
550
  const dispatchedAtEpochMs = Date.parse(target.dispatchedAt || '');
537
551
  const candidatePid = Math.max(0, Math.floor(Number(device?.pid) || 0));
538
- const candidateProductVersion = String(device?.productVersion || '');
539
- const candidateAgentVersion = String(device?.agentVersion || '');
540
- const qualifiesForStability = device?.connected === true
552
+ const candidateProductVersion = String(device?.productVersion || '');
553
+ const candidateAgentVersion = String(device?.agentVersion || '');
554
+ const supervisorProof = device?.clientUpdateProof;
555
+ const supervisorProofId = [
556
+ String(supervisorProof?.operationId || ''),
557
+ String(supervisorProof?.attemptId || ''),
558
+ String(supervisorProof?.sessionId || ''),
559
+ String(supervisorProof?.agentPid || ''),
560
+ String(supervisorProof?.launcherPid || ''),
561
+ String(supervisorProof?.completedAt || '')
562
+ ].join(':');
563
+ const hasSupervisorProof = !!supervisorProof
564
+ && String(supervisorProof.operationId || '') === run.operationId
565
+ && !!String(supervisorProof.attemptId || '')
566
+ && String(supervisorProof.sessionId || '') === sessionId
567
+ && Number(supervisorProof.agentPid || 0) === candidatePid
568
+ && Number(supervisorProof.launcherPid || 0) > 1
569
+ && cleanVersion(supervisorProof.productVersion) === cleanVersion(candidateProductVersion)
570
+ && cleanVersion(supervisorProof.agentVersion) === cleanVersion(candidateAgentVersion)
571
+ && Date.parse(String(supervisorProof.receivedAt || '')) >= connectedAtEpochMs;
572
+ const requiredStabilityMs = hasSupervisorProof
573
+ ? run.verifiedClientRestartStabilityMs
574
+ : run.clientRestartStabilityMs;
575
+ const qualifiesForStability = device?.connected === true
541
576
  && !!sessionId
542
577
  && candidatePid > 1
543
578
  && sessionId !== target.dispatchedSessionId
@@ -546,9 +581,10 @@ export function createLiveDeskUpdateManager({
546
581
  const sameCandidate = qualifiesForStability
547
582
  && target.candidateSessionId === sessionId
548
583
  && target.candidatePid === candidatePid
549
- && target.candidateConnectedAt === connectedAt
550
- && target.candidateProductVersion === candidateProductVersion
551
- && target.candidateAgentVersion === candidateAgentVersion;
584
+ && target.candidateConnectedAt === connectedAt
585
+ && target.candidateProductVersion === candidateProductVersion
586
+ && target.candidateAgentVersion === candidateAgentVersion
587
+ && target.candidateSupervisorProofId === (hasSupervisorProof ? supervisorProofId : '');
552
588
 
553
589
  if (!qualifiesForStability) {
554
590
  resetReconnectCandidate(target);
@@ -556,12 +592,18 @@ export function createLiveDeskUpdateManager({
556
592
  target.candidateSessionId = sessionId;
557
593
  target.candidatePid = candidatePid;
558
594
  target.candidateConnectedAt = connectedAt;
559
- target.candidateProductVersion = candidateProductVersion;
560
- target.candidateAgentVersion = candidateAgentVersion;
561
- target.candidateSince = new Date(currentTime).toISOString();
562
- target.stabilityDeadlineAt = new Date(
563
- currentTime + run.clientRestartStabilityMs
564
- ).toISOString();
595
+ target.candidateProductVersion = candidateProductVersion;
596
+ target.candidateAgentVersion = candidateAgentVersion;
597
+ target.candidateSupervisorProofId = hasSupervisorProof ? supervisorProofId : '';
598
+ target.verificationSource = hasSupervisorProof ? 'supervisor-proof' : 'hub-session';
599
+ target.requiredStabilityMs = requiredStabilityMs;
600
+ target.supervisorProofAt = hasSupervisorProof
601
+ ? String(supervisorProof.receivedAt || supervisorProof.completedAt || '')
602
+ : '';
603
+ target.candidateSince = new Date(currentTime).toISOString();
604
+ target.stabilityDeadlineAt = new Date(
605
+ currentTime + requiredStabilityMs
606
+ ).toISOString();
565
607
  }
566
608
 
567
609
  if (qualifiesForStability
@@ -646,8 +688,9 @@ export function createLiveDeskUpdateManager({
646
688
  latestManagerVersion: release.latestManagerVersion,
647
689
  latestClientVersion: release.latestClientVersion,
648
690
  needsHubRestart,
649
- batchSize: effectiveClientBatchSize,
650
- clientRestartStabilityMs: effectiveClientRestartStabilityMs,
691
+ batchSize: effectiveClientBatchSize,
692
+ clientRestartStabilityMs: effectiveClientRestartStabilityMs,
693
+ verifiedClientRestartStabilityMs: effectiveVerifiedClientRestartStabilityMs,
651
694
  error: '',
652
695
  targets: targets.map(device => ({
653
696
  deviceId: String(device.deviceId || ''),
@@ -666,9 +709,13 @@ export function createLiveDeskUpdateManager({
666
709
  candidateSessionId: '',
667
710
  candidatePid: 0,
668
711
  candidateConnectedAt: '',
669
- candidateProductVersion: '',
670
- candidateAgentVersion: '',
671
- candidateSince: '',
712
+ candidateProductVersion: '',
713
+ candidateAgentVersion: '',
714
+ candidateSupervisorProofId: '',
715
+ verificationSource: '',
716
+ requiredStabilityMs: 0,
717
+ supervisorProofAt: '',
718
+ candidateSince: '',
672
719
  stabilityDeadlineAt: '',
673
720
  completedAt: '',
674
721
  error: ''
@@ -698,7 +745,9 @@ export function createLiveDeskUpdateManager({
698
745
 
699
746
  const handleRemoteEvent = (type, event) => {
700
747
  if (!run || run.state !== 'waiting-for-clients') return;
701
- if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
748
+ if (type === 'RemoteDeviceConnected'
749
+ || type === 'RemoteDeviceDisconnected'
750
+ || type === 'RemoteClientUpdateVerified') {
702
751
  verifyTargets();
703
752
  return;
704
753
  }
package/src/remote-hub.js CHANGED
@@ -286,7 +286,7 @@ function normalizePort(value) {
286
286
  return clampNumber(value, 0, 65535, DEFAULT_REMOTE_HUB_PORT);
287
287
  }
288
288
 
289
- function safeString(value, maxLength = 200) {
289
+ function safeString(value, maxLength = 200) {
290
290
  return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
291
291
  }
292
292
 
@@ -1119,9 +1119,55 @@ function getAccountRouteEndpointReason(endpoint, context = {}) {
1119
1119
  return 'ok';
1120
1120
  }
1121
1121
 
1122
- function safeText(value, maxLength = 1000) {
1123
- return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
1124
- }
1122
+ function safeText(value, maxLength = 1000) {
1123
+ return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
1124
+ }
1125
+
1126
+ function normalizeClientUpdateVerifiedProof(device, message, receivedAt) {
1127
+ const operationId = safeString(message?.operationId, 180);
1128
+ const attemptId = safeString(message?.attemptId, 180);
1129
+ const productVersion = safeString(message?.productVersion, 40);
1130
+ const agentVersion = safeString(message?.agentVersion, 40);
1131
+ const completedAt = safeString(message?.completedAt, 64);
1132
+ const launcherPid = Math.max(0, Math.floor(Number(message?.launcherPid) || 0));
1133
+ const agentPid = Math.max(0, Math.floor(Number(message?.agentPid) || 0));
1134
+ const supervisorPid = Math.max(0, Math.floor(Number(message?.supervisorPid) || 0));
1135
+ if (!operationId
1136
+ || !attemptId
1137
+ || !productVersion
1138
+ || !agentVersion
1139
+ || !Number.isFinite(Date.parse(completedAt))
1140
+ || launcherPid <= 1
1141
+ || agentPid <= 1
1142
+ || supervisorPid <= 1
1143
+ || agentPid !== Number(device?.pid || 0)
1144
+ || productVersion !== String(device?.productVersion || '')
1145
+ || agentVersion !== String(device?.agentVersion || '')) {
1146
+ return null;
1147
+ }
1148
+ return {
1149
+ operationId,
1150
+ attemptId,
1151
+ sessionId: String(device.sessionId || ''),
1152
+ launcherPid,
1153
+ agentPid,
1154
+ supervisorPid,
1155
+ productVersion,
1156
+ agentVersion,
1157
+ completedAt,
1158
+ receivedAt
1159
+ };
1160
+ }
1161
+
1162
+ function normalizeRemoteKeyboardKey(value) {
1163
+ const raw = String(value ?? '').replace(/\0/g, '');
1164
+ return raw === ' ' ? ' ' : safeString(raw, 80);
1165
+ }
1166
+
1167
+ function normalizeRemoteKeyboardText(value) {
1168
+ const raw = String(value ?? '').replace(/\0/g, '');
1169
+ return raw === ' ' ? ' ' : safeText(raw, 256);
1170
+ }
1125
1171
 
1126
1172
  function safeTaskData(value) {
1127
1173
  if (value === undefined || value === null) return undefined;
@@ -1202,14 +1248,14 @@ function readRawRemoteInputMonitorIndex(input) {
1202
1248
  return undefined;
1203
1249
  }
1204
1250
 
1205
- function normalizeRemoteInputEvent(value = {}) {
1251
+ function normalizeRemoteInputEvent(value = {}) {
1206
1252
  const input = value && typeof value === 'object' ? value : {};
1207
1253
  const type = safeString(input.type || input.Type, 48);
1208
1254
  const normalizedX = Number(input.normalizedX ?? input.NormalizedX);
1209
1255
  const normalizedY = Number(input.normalizedY ?? input.NormalizedY);
1210
1256
  const deltaX = Number(input.deltaX ?? input.DeltaX);
1211
1257
  const deltaY = Number(input.deltaY ?? input.DeltaY);
1212
- const keyCode = Number(input.keyCode ?? input.KeyCode);
1258
+ const keyCode = Number(input.keyCode ?? input.KeyCode);
1213
1259
  const location = Number(input.location ?? input.Location);
1214
1260
  const monitorIndex = parseExactLiveStreamMonitorIndex(readRawRemoteInputMonitorIndex(input));
1215
1261
  const inputSeq = Number(input.inputSeq ?? input.InputSeq);
@@ -1217,13 +1263,18 @@ function normalizeRemoteInputEvent(value = {}) {
1217
1263
  const hubReceivedAtEpochMs = Number(input.hubReceivedAtEpochMs ?? input.HubReceivedAtEpochMs);
1218
1264
  const captureGeneration = Number(input.captureGeneration ?? input.CaptureGeneration);
1219
1265
  const rawPressedCodes = input.pressedCodes ?? input.PressedCodes;
1220
- const pressedCodes = Array.isArray(rawPressedCodes)
1266
+ const pressedCodes = Array.isArray(rawPressedCodes)
1221
1267
  ? [...new Set(rawPressedCodes
1222
1268
  .map(code => safeString(code, 80))
1223
1269
  .filter(Boolean))]
1224
1270
  .slice(0, 64)
1225
- : null;
1226
- return {
1271
+ : null;
1272
+ const key = normalizeRemoteKeyboardKey(input.key ?? input.Key);
1273
+ let code = safeString(input.code ?? input.Code, 80);
1274
+ if ((!code || code === 'Unidentified') && (key === ' ' || key === 'Spacebar' || keyCode === 32)) {
1275
+ code = 'Space';
1276
+ }
1277
+ return {
1227
1278
  type,
1228
1279
  // A missing monitor is not equivalent to the primary display. Control
1229
1280
  // input must prove the exact monitor owned by its capture generation.
@@ -1233,11 +1284,11 @@ function normalizeRemoteInputEvent(value = {}) {
1233
1284
  button: safeString(input.button || input.Button, 24),
1234
1285
  deltaX: Number.isFinite(deltaX) ? Math.max(-4096, Math.min(4096, deltaX)) : 0,
1235
1286
  deltaY: Number.isFinite(deltaY) ? Math.max(-4096, Math.min(4096, deltaY)) : 0,
1236
- key: safeString(input.key || input.Key, 80),
1237
- code: safeString(input.code || input.Code, 80),
1287
+ key,
1288
+ code,
1238
1289
  keyCode: Number.isFinite(keyCode) ? Math.max(0, Math.min(65535, Math.trunc(keyCode))) : 0,
1239
1290
  location: Number.isFinite(location) ? Math.max(0, Math.min(255, Math.trunc(location))) : 0,
1240
- text: safeText(input.text || input.Text, 256),
1291
+ text: normalizeRemoteKeyboardText(input.text ?? input.Text),
1241
1292
  repeat: input.repeat === true || input.Repeat === true,
1242
1293
  pressedCodes,
1243
1294
  shiftKey: input.shiftKey === true || input.ShiftKey === true,
@@ -1611,8 +1662,9 @@ function serializeDevice(device, options = {}) {
1611
1662
  byteLength: device.latestAudioFrame.byteLength
1612
1663
  }
1613
1664
  : null,
1614
- latestAudioStatus: device.latestAudioStatus ? { ...device.latestAudioStatus } : null,
1615
- udp: device.udp ? { ...device.udp } : { enabled: false, state: 'tcp-only', ready: false },
1665
+ latestAudioStatus: device.latestAudioStatus ? { ...device.latestAudioStatus } : null,
1666
+ clientUpdateProof: device.clientUpdateProof ? { ...device.clientUpdateProof } : null,
1667
+ udp: device.udp ? { ...device.udp } : { enabled: false, state: 'tcp-only', ready: false },
1616
1668
  latestTask: device.latestTask ? { ...device.latestTask } : null,
1617
1669
  synthetic: device.synthetic === true,
1618
1670
  recentTasks: Array.isArray(device.recentTasks)
@@ -4412,8 +4464,9 @@ export function createRemoteHub(options = {}) {
4412
4464
  liveCapturePause: existing?.liveCapturePause || null,
4413
4465
  activeAudioStream: null,
4414
4466
  latestAudioFrame: null,
4415
- latestAudioStatus: null,
4416
- udp: {
4467
+ latestAudioStatus: null,
4468
+ clientUpdateProof: null,
4469
+ udp: {
4417
4470
  enabled: capabilities.udpP2p === true,
4418
4471
  state: 'tcp-only',
4419
4472
  ready: false,
@@ -7426,7 +7479,7 @@ export function createRemoteHub(options = {}) {
7426
7479
  device.counters.statusReceived += 1;
7427
7480
  emitRemoteEvent('RemoteDeviceStatus', device);
7428
7481
  break;
7429
- case 'command.result':
7482
+ case 'command.result':
7430
7483
  case 'command.error':
7431
7484
  device.counters.commandResultsReceived += 1;
7432
7485
  {
@@ -7454,9 +7507,23 @@ export function createRemoteHub(options = {}) {
7454
7507
  commandId: safeString(message.commandId, 128),
7455
7508
  result: message.result ?? null,
7456
7509
  error: safeString(message.error, 500)
7457
- });
7458
- break;
7459
- case 'input.applied':
7510
+ });
7511
+ break;
7512
+ case 'client.update.verified': {
7513
+ const receivedAt = new Date().toISOString();
7514
+ const proof = normalizeClientUpdateVerifiedProof(device, message, receivedAt);
7515
+ if (!proof) {
7516
+ logWarn(
7517
+ 'remote',
7518
+ `ignored invalid Client update supervisor proof from ${device.deviceName} (${device.deviceId})`
7519
+ );
7520
+ break;
7521
+ }
7522
+ device.clientUpdateProof = proof;
7523
+ emitRemoteEvent('RemoteClientUpdateVerified', device, { proof });
7524
+ break;
7525
+ }
7526
+ case 'input.applied':
7460
7527
  case 'input.error':
7461
7528
  handleRemoteInputOutcome(device, message, 'main');
7462
7529
  break;
package/src/server.js CHANGED
@@ -2710,7 +2710,7 @@ function enqueueFramePacketForClient(ws, packetOwner, meta) {
2710
2710
  }
2711
2711
  // FRAME_LANE_GENERATION_CONTRACT_END
2712
2712
 
2713
- function updateFrameSubscription(ws, payload = {}) {
2713
+ function updateFrameSubscription(ws, payload = {}) {
2714
2714
  const previousDeviceIds = ws.liveDeskDeviceIds instanceof Set
2715
2715
  ? new Set(ws.liveDeskDeviceIds)
2716
2716
  : new Set();
@@ -2743,10 +2743,43 @@ function updateFrameSubscription(ws, payload = {}) {
2743
2743
  const targetIds = newlySubscribedIds.length > 0 ? newlySubscribedIds : deviceIds;
2744
2744
  for (const deviceId of targetIds.slice(0, 80)) {
2745
2745
  startFrameSubscriptionLive(ws, 'subscribe', deviceId, { reuseExisting: true });
2746
- }
2747
- }
2748
-
2749
- function startFrameSubscriptionLive(
2746
+ }
2747
+ }
2748
+
2749
+ function refreshFrameSubscriptionLive(ws, payload = {}) {
2750
+ if (!ws || ws.readyState !== 1) {
2751
+ return;
2752
+ }
2753
+ const requestedIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
2754
+ const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
2755
+ const targetIds = requestedIds.length > 0
2756
+ ? requestedIds.filter(deviceId => ws.liveDeskDeviceIds?.has?.(deviceId))
2757
+ : subscribedIds;
2758
+ if (targetIds.length === 0) {
2759
+ return;
2760
+ }
2761
+ ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(
2762
+ payload.autoStartLive ?? payload.startLive ?? ws.liveDeskAutoStart ?? ''
2763
+ ));
2764
+ ws.liveDeskLiveOptions = normalizeLiveOptions({
2765
+ ...(ws.liveDeskLiveOptions || {}),
2766
+ ...payload,
2767
+ forceRestart: false,
2768
+ reuseExisting: true,
2769
+ monitorSelections: {
2770
+ ...(ws.liveDeskLiveOptions?.monitorSelections || {}),
2771
+ ...(payload.monitorSelections || {})
2772
+ }
2773
+ });
2774
+ for (const deviceId of targetIds.slice(0, 80)) {
2775
+ startFrameSubscriptionLive(ws, 'watchdog', deviceId, {
2776
+ ...ws.liveDeskLiveOptions,
2777
+ reuseExisting: true
2778
+ });
2779
+ }
2780
+ }
2781
+
2782
+ function startFrameSubscriptionLive(
2750
2783
  ws,
2751
2784
  reason = 'subscribe',
2752
2785
  onlyDeviceId = '',
@@ -5494,9 +5527,11 @@ frameWss.on('connection', (ws, req) => {
5494
5527
  ws.on('message', data => {
5495
5528
  try {
5496
5529
  const payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
5497
- if (payload?.type === 'subscribe') {
5498
- updateFrameSubscription(ws, payload);
5499
- } else if (payload?.type === 'restart-live') {
5530
+ if (payload?.type === 'subscribe') {
5531
+ updateFrameSubscription(ws, payload);
5532
+ } else if (payload?.type === 'refresh-live') {
5533
+ refreshFrameSubscriptionLive(ws, payload);
5534
+ } else if (payload?.type === 'restart-live') {
5500
5535
  restartFrameSubscriptionLive(ws, payload);
5501
5536
  }
5502
5537
  } catch {