livedesk 0.1.431 → 0.1.433

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.189",
3
+ "version": "0.1.191",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,10 +41,10 @@
41
41
  "ws": "^8.18.3"
42
42
  },
43
43
  "optionalDependencies": {
44
- "@livedesk/fast-linux-x64": "0.1.396",
45
- "@livedesk/fast-osx-arm64": "0.1.396",
46
- "@livedesk/fast-osx-x64": "0.1.396",
47
- "@livedesk/fast-win-x64": "0.1.396"
44
+ "@livedesk/fast-linux-x64": "0.1.398",
45
+ "@livedesk/fast-osx-arm64": "0.1.398",
46
+ "@livedesk/fast-osx-x64": "0.1.398",
47
+ "@livedesk/fast-win-x64": "0.1.398"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
@@ -49,7 +49,20 @@ function normalizeAccountAvatarUrl(value) {
49
49
  }
50
50
  }
51
51
 
52
+ function readAccessTokenProfile(accessToken) {
53
+ const token = normalizeString(accessToken, 8192);
54
+ const payloadSegment = token.split('.')[1] || '';
55
+ if (!payloadSegment) return {};
56
+ try {
57
+ const payload = JSON.parse(Buffer.from(payloadSegment, 'base64url').toString('utf8'));
58
+ return payload && typeof payload === 'object' ? payload : {};
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+
52
64
  function readClientAccountProfile(sessionOrUser) {
65
+ const tokenProfile = readAccessTokenProfile(sessionOrUser?.access_token);
53
66
  const user = sessionOrUser?.user && typeof sessionOrUser.user === 'object'
54
67
  ? sessionOrUser.user
55
68
  : sessionOrUser && typeof sessionOrUser === 'object'
@@ -58,12 +71,19 @@ function readClientAccountProfile(sessionOrUser) {
58
71
  const metadata = user.user_metadata && typeof user.user_metadata === 'object'
59
72
  ? user.user_metadata
60
73
  : {};
61
- const email = normalizeString(user.email, 320);
74
+ const tokenMetadata = tokenProfile.user_metadata && typeof tokenProfile.user_metadata === 'object'
75
+ ? tokenProfile.user_metadata
76
+ : {};
77
+ const email = normalizeString(user.email || tokenProfile.email, 320);
62
78
  const name = normalizeString(
63
79
  metadata.full_name
64
80
  || metadata.name
65
81
  || metadata.preferred_username
82
+ || tokenMetadata.full_name
83
+ || tokenMetadata.name
84
+ || tokenMetadata.preferred_username
66
85
  || user.name
86
+ || tokenProfile.name
67
87
  || email
68
88
  || user.id,
69
89
  160
@@ -71,6 +91,8 @@ function readClientAccountProfile(sessionOrUser) {
71
91
  const avatarUrl = normalizeAccountAvatarUrl(
72
92
  metadata.avatar_url
73
93
  || metadata.picture
94
+ || tokenMetadata.avatar_url
95
+ || tokenMetadata.picture
74
96
  || user.avatar_url
75
97
  || user.picture
76
98
  );
@@ -79,7 +101,11 @@ function readClientAccountProfile(sessionOrUser) {
79
101
 
80
102
  function mergeClientAccountProfile(session, sourceUser) {
81
103
  const profile = readClientAccountProfile(sourceUser);
104
+ const existingUserMetadata = session?.user?.user_metadata && typeof session.user.user_metadata === 'object'
105
+ ? session.user.user_metadata
106
+ : {};
82
107
  const userMetadata = {
108
+ ...existingUserMetadata,
83
109
  ...(profile.name ? { full_name: profile.name } : {}),
84
110
  ...(profile.avatarUrl ? { avatar_url: profile.avatarUrl } : {})
85
111
  };
@@ -1275,7 +1301,11 @@ export function createClientRuntimeServer(options = {}) {
1275
1301
  setImmediate(() => {
1276
1302
  void (async () => {
1277
1303
  try {
1278
- const normalizedSession = mergeClientAccountProfile(saved.session, savedSession?.user);
1304
+ // Older persisted sessions kept only id/email on `user`, while
1305
+ // Supabase still carries Google name/avatar claims in the
1306
+ // access-token payload. Recover those display-only claims
1307
+ // locally before any best-effort network hydration.
1308
+ const normalizedSession = mergeClientAccountProfile(saved.session, savedSession);
1279
1309
  const hydratedSession = await hydrateClientAccountProfile(normalizedSession);
1280
1310
  if (!writeSavedSession(hydratedSession)) throw new Error('refresh-token-required');
1281
1311
  state.auth.persisted = true;
@@ -685,7 +685,9 @@ function normalizeRemoteInputEvent(value = {}) {
685
685
  const captureGeneration = Number(input.captureGeneration ?? input.CaptureGeneration);
686
686
  return {
687
687
  type,
688
- monitorIndex: Number.isFinite(monitorIndex) ? normalizeMonitorIndex(monitorIndex) : 0,
688
+ // A missing monitor is not equivalent to the primary display. Control
689
+ // input must prove the exact monitor owned by its capture generation.
690
+ monitorIndex: Number.isFinite(monitorIndex) ? normalizeMonitorIndex(monitorIndex) : null,
689
691
  normalizedX: Number.isFinite(normalizedX) ? Math.max(0, Math.min(1, normalizedX)) : undefined,
690
692
  normalizedY: Number.isFinite(normalizedY) ? Math.max(0, Math.min(1, normalizedY)) : undefined,
691
693
  button: safeString(input.button || input.Button, 24),
@@ -2144,9 +2146,12 @@ export function createRemoteHub(options = {}) {
2144
2146
  byteLength: frame.byteLength
2145
2147
  });
2146
2148
  streamState.latestFrame = frame;
2147
- streamState.lastFrameAt = frame.receivedAt;
2148
- streamState.lastFrameSeq = frame.frameSeq;
2149
- streamState.framesReceived = (streamState.framesReceived || 0) + 1;
2149
+ streamState.lastFrameAt = frame.receivedAt;
2150
+ streamState.lastFrameSeq = frame.frameSeq;
2151
+ streamState.framesReceived = (streamState.framesReceived || 0) + 1;
2152
+ // A synthetic PNG is a complete independently decodable surface and
2153
+ // therefore key-frame equivalent for Control readiness.
2154
+ streamState.readyFrameReceived = true;
2150
2155
  device.lastSeenAt = frame.receivedAt;
2151
2156
  device.counters.liveFramesReceived += 1;
2152
2157
  emitRemoteEvent('RemoteFrameReceived', device, {
@@ -3329,6 +3334,20 @@ export function createRemoteHub(options = {}) {
3329
3334
  return {
3330
3335
  deviceId: device.deviceId,
3331
3336
  commandId: safeString(message.commandId || message.CommandId || pending?.commandId, 128),
3337
+ hubConnectionId: safeString(
3338
+ message.hubConnectionId
3339
+ || message.HubConnectionId
3340
+ || result.hubConnectionId
3341
+ || result.HubConnectionId
3342
+ || pending?.hubConnectionId,
3343
+ 128),
3344
+ inputEventId: safeString(
3345
+ message.inputEventId
3346
+ || message.InputEventId
3347
+ || result.inputEventId
3348
+ || result.InputEventId
3349
+ || pending?.inputEventId,
3350
+ 128),
3332
3351
  inputSeq,
3333
3352
  inputType: safeString(
3334
3353
  message.inputType
@@ -3337,6 +3356,13 @@ export function createRemoteHub(options = {}) {
3337
3356
  || result.InputType
3338
3357
  || pending?.inputType,
3339
3358
  48),
3359
+ monitorIndex: Number(
3360
+ message.monitorIndex
3361
+ ?? message.MonitorIndex
3362
+ ?? result.monitorIndex
3363
+ ?? result.MonitorIndex
3364
+ ?? pending?.monitorIndex
3365
+ ?? 0) || 0,
3340
3366
  issuedAtEpochMs: Number(
3341
3367
  message.issuedAtEpochMs
3342
3368
  ?? message.IssuedAtEpochMs
@@ -3602,13 +3628,35 @@ export function createRemoteHub(options = {}) {
3602
3628
  : 0;
3603
3629
  }
3604
3630
 
3605
- function readFrameStageMs(message, property) {
3606
- const value = Number(message?.[property]);
3607
- return Number.isFinite(value) && value >= 0
3608
- ? Math.round(value)
3609
- : 0;
3610
- }
3611
-
3631
+ function readFrameStageMs(message, property) {
3632
+ const value = Number(message?.[property]);
3633
+ return Number.isFinite(value) && value >= 0
3634
+ ? Math.round(value)
3635
+ : 0;
3636
+ }
3637
+
3638
+ function readOptionalFrameStageMs(message, property) {
3639
+ const rawValue = message?.[property];
3640
+ if (rawValue === undefined || rawValue === null || rawValue === '') {
3641
+ return undefined;
3642
+ }
3643
+ const value = Number(rawValue);
3644
+ return Number.isFinite(value) && value >= 0
3645
+ ? Math.round(value)
3646
+ : undefined;
3647
+ }
3648
+
3649
+ function hasFrameCaptureTiming(message) {
3650
+ if (message?.captureTimingAvailable === false) {
3651
+ return false;
3652
+ }
3653
+ if (message?.captureTimingAvailable === true) {
3654
+ return true;
3655
+ }
3656
+ return ['captureP95Ms', 'captureAverageMs', 'captureStageMs']
3657
+ .some(property => readOptionalFrameStageMs(message, property) !== undefined);
3658
+ }
3659
+
3612
3660
  function computeSameContentStreak(previousFrame, contentHash) {
3613
3661
  if (!contentHash || !previousFrame?.contentHash || previousFrame.contentHash !== contentHash) {
3614
3662
  return 0;
@@ -3682,7 +3730,7 @@ export function createRemoteHub(options = {}) {
3682
3730
  byteLength,
3683
3731
  transport,
3684
3732
  contentHash,
3685
- captureMs: readFrameCaptureMs(message),
3733
+ captureMs: readFrameCaptureMs(message),
3686
3734
  captureStageMs: readFrameStageMs(message, 'captureStageMs'),
3687
3735
  convertMs: readFrameStageMs(message, 'convertMs'),
3688
3736
  compressMs: readFrameStageMs(message, 'compressMs'),
@@ -3774,6 +3822,7 @@ export function createRemoteHub(options = {}) {
3774
3822
  lastFrameAt: '',
3775
3823
  lastFrameSeq: 0,
3776
3824
  framesReceived: 0,
3825
+ readyFrameReceived: false,
3777
3826
  latestFrame: null,
3778
3827
  stoppedAt: '',
3779
3828
  stopReason: ''
@@ -3991,6 +4040,8 @@ export function createRemoteHub(options = {}) {
3991
4040
  && (activeCaptureGeneration <= 0
3992
4041
  || (frameCaptureGeneration > 0
3993
4042
  && frameCaptureGeneration === activeCaptureGeneration));
4043
+ const readyFrameReceivedBeforeFrame = streamState.readyFrameReceived === true;
4044
+ const captureTimingAvailable = hasFrameCaptureTiming(message);
3994
4045
  device.latestLiveFrame = {
3995
4046
  streamId,
3996
4047
  frameSeq,
@@ -4037,10 +4088,11 @@ export function createRemoteHub(options = {}) {
4037
4088
  transferProtocolVersion: transfer.transferProtocolVersion,
4038
4089
  handshake: transfer.handshake,
4039
4090
  fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : streamState.fps,
4040
- requestedFps: Number.isFinite(Number(message.requestedFps)) ? Number(message.requestedFps) : Number(streamState.fps || 0),
4041
- effectiveFps: Number.isFinite(Number(message.effectiveFps)) ? Number(message.effectiveFps) : Number(message.fps || streamState.fps || 0),
4042
- captureAverageMs: readFrameStageMs(message, 'captureAverageMs'),
4043
- captureP95Ms: readFrameStageMs(message, 'captureP95Ms'),
4091
+ requestedFps: Number.isFinite(Number(message.requestedFps)) ? Number(message.requestedFps) : Number(streamState.fps || 0),
4092
+ effectiveFps: Number.isFinite(Number(message.effectiveFps)) ? Number(message.effectiveFps) : Number(message.fps || streamState.fps || 0),
4093
+ captureTimingAvailable,
4094
+ captureAverageMs: captureTimingAvailable ? readOptionalFrameStageMs(message, 'captureAverageMs') : undefined,
4095
+ captureP95Ms: captureTimingAvailable ? readOptionalFrameStageMs(message, 'captureP95Ms') : undefined,
4044
4096
  slowFrameCount: Number.isFinite(Number(message.slowFrameCount)) ? Number(message.slowFrameCount) : 0,
4045
4097
  captureHelperRestarts: Number.isFinite(Number(message.captureHelperRestarts)) ? Number(message.captureHelperRestarts) : 0,
4046
4098
  capturedAt,
@@ -4048,8 +4100,8 @@ export function createRemoteHub(options = {}) {
4048
4100
  byteLength,
4049
4101
  transport,
4050
4102
  contentHash,
4051
- captureMs: readFrameCaptureMs(message),
4052
- captureStageMs: readFrameStageMs(message, 'captureStageMs'),
4103
+ captureMs: captureTimingAvailable ? readOptionalFrameStageMs(message, 'captureMs') : undefined,
4104
+ captureStageMs: captureTimingAvailable ? readOptionalFrameStageMs(message, 'captureStageMs') : undefined,
4053
4105
  convertMs: readFrameStageMs(message, 'convertMs'),
4054
4106
  compressMs: readFrameStageMs(message, 'compressMs'),
4055
4107
  captureSourceSerial: Number.isFinite(Number(message.captureSourceSerial)) ? Number(message.captureSourceSerial) : 0,
@@ -4073,9 +4125,12 @@ export function createRemoteHub(options = {}) {
4073
4125
  mimeType: device.latestLiveFrame.mimeType,
4074
4126
  byteLength: device.latestLiveFrame.byteLength
4075
4127
  });
4076
- streamState.lastFrameAt = device.lastSeenAt;
4077
- streamState.lastFrameSeq = frameSeq;
4078
- streamState.framesReceived = (streamState.framesReceived || 0) + 1;
4128
+ streamState.lastFrameAt = device.lastSeenAt;
4129
+ streamState.lastFrameSeq = frameSeq;
4130
+ streamState.framesReceived = (streamState.framesReceived || 0) + 1;
4131
+ streamState.readyFrameReceived = readyFrameReceivedBeforeFrame
4132
+ || (currentGenerationVerified
4133
+ && liveFrameSatisfiesReadiness(streamState, device.latestLiveFrame));
4079
4134
  streamState.mode = transfer.mode;
4080
4135
  streamState.frameMode = transfer.frameMode;
4081
4136
  streamState.frameProfile = transfer.frameProfile;
@@ -4093,7 +4148,9 @@ export function createRemoteHub(options = {}) {
4093
4148
  streamState.monitorCount = device.latestLiveFrame.monitorCount;
4094
4149
  ensureDeviceLiveStreams(device).set(streamId, streamState);
4095
4150
  device.counters.liveFramesReceived += 1;
4096
- if (streamState.framesReceived === 1 && liveStreamHasCurrentFrame(streamState)) {
4151
+ if (!readyFrameReceivedBeforeFrame
4152
+ && streamState.readyFrameReceived === true
4153
+ && liveStreamHasCurrentFrame(streamState)) {
4097
4154
  emitRemoteEvent('RemoteLiveStreamReady', device, {
4098
4155
  streamId,
4099
4156
  commandId: streamState.commandId,
@@ -4493,23 +4550,12 @@ export function createRemoteHub(options = {}) {
4493
4550
 
4494
4551
  if (state.inputOnly) {
4495
4552
  device.inputLastSeenAt = new Date().toISOString();
4496
- if (message.type === 'input.applied') {
4553
+ if (message.type === 'input.applied' || message.type === 'input.error') {
4497
4554
  handleRemoteInputOutcome(device, {
4498
4555
  ...message,
4499
4556
  agentApplyMs: Number(message.agentApplyMs || 0) || 0
4500
4557
  }, 'input');
4501
4558
  }
4502
- if (message.type === 'input.error') {
4503
- const pending = takePendingInputFallback(
4504
- device,
4505
- message.commandId || message.CommandId,
4506
- message.inputSeq ?? message.InputSeq);
4507
- emitEvent('RemoteInputError', {
4508
- ...buildRemoteInputOutcome(device, message, pending),
4509
- error: safeString(message.error || message.Error, 600) || 'remote-input-failed',
4510
- failedAtEpochMs: Number(message.failedAtEpochMs ?? message.FailedAtEpochMs ?? 0) || 0
4511
- });
4512
- }
4513
4559
  return;
4514
4560
  }
4515
4561
  if (state.frameOnly) {
@@ -5292,6 +5338,15 @@ export function createRemoteHub(options = {}) {
5292
5338
  activeCaptureGeneration: Number(controlStream.captureGeneration || 0)
5293
5339
  };
5294
5340
  }
5341
+ const activeMonitorIndex = normalizeMonitorIndex(controlStream.monitorIndex);
5342
+ if (!Number.isInteger(normalized.monitorIndex)
5343
+ || normalized.monitorIndex !== activeMonitorIndex) {
5344
+ return {
5345
+ ok: false,
5346
+ error: 'STALE_CONTROL_MONITOR',
5347
+ activeMonitorIndex
5348
+ };
5349
+ }
5295
5350
 
5296
5351
  const inputSocket = device.inputSocket;
5297
5352
  if (inputSocket && !inputSocket.destroyed) {
@@ -5311,7 +5366,8 @@ export function createRemoteHub(options = {}) {
5311
5366
  inputSocket: true,
5312
5367
  sessionId: device.sessionId,
5313
5368
  commandId: controlStream.commandId,
5314
- captureGeneration: controlStream.captureGeneration
5369
+ captureGeneration: controlStream.captureGeneration,
5370
+ monitorIndex: activeMonitorIndex
5315
5371
  };
5316
5372
  }
5317
5373
 
@@ -5332,6 +5388,9 @@ export function createRemoteHub(options = {}) {
5332
5388
  commandId: fallback.commandId,
5333
5389
  inputSeq: normalized.inputSeq,
5334
5390
  inputType: normalized.type,
5391
+ monitorIndex: normalized.monitorIndex,
5392
+ hubConnectionId: normalized.hubConnectionId,
5393
+ inputEventId: normalized.inputEventId,
5335
5394
  issuedAtEpochMs: normalized.issuedAtEpochMs,
5336
5395
  hubReceivedAtEpochMs: normalized.hubReceivedAtEpochMs,
5337
5396
  hubForwardedAtEpochMs,
@@ -5348,6 +5407,7 @@ export function createRemoteHub(options = {}) {
5348
5407
  deliveryCommandId: fallback.commandId,
5349
5408
  commandId: controlStream.commandId,
5350
5409
  captureGeneration: controlStream.captureGeneration,
5410
+ monitorIndex: activeMonitorIndex,
5351
5411
  inputSeq: normalized.inputSeq
5352
5412
  }
5353
5413
  : {
@@ -5855,12 +5915,31 @@ export function createRemoteHub(options = {}) {
5855
5915
  return Number.isFinite(startedAt) && Date.now() - startedAt < LIVE_STREAM_REPLACEMENT_PENDING_MS;
5856
5916
  }
5857
5917
 
5918
+ function liveStreamRequiresReadyKeyFrame(activeLiveStream) {
5919
+ if (safeString(activeLiveStream?.streamPurpose, 24).toLowerCase() !== 'control') {
5920
+ return false;
5921
+ }
5922
+ const codec = safeString(activeLiveStream?.codec, 80).toLowerCase();
5923
+ const mode = safeString(activeLiveStream?.frameMode || activeLiveStream?.mode, 80).toLowerCase();
5924
+ return codec.includes('h264') || mode === 'mode3-h264-hw';
5925
+ }
5926
+
5927
+ function liveFrameSatisfiesReadiness(activeLiveStream, frame) {
5928
+ return !liveStreamRequiresReadyKeyFrame(activeLiveStream)
5929
+ || frame?.isKeyFrame === true
5930
+ || safeString(frame?.chunkType, 20).toLowerCase() === 'key';
5931
+ }
5932
+
5858
5933
  function liveStreamHasCurrentFrame(activeLiveStream) {
5859
5934
  if (!activeLiveStream?.active
5860
5935
  || activeLiveStream.open !== true
5861
5936
  || Number(activeLiveStream.framesReceived || 0) < 1) {
5862
5937
  return false;
5863
5938
  }
5939
+ if (liveStreamRequiresReadyKeyFrame(activeLiveStream)
5940
+ && activeLiveStream.readyFrameReceived !== true) {
5941
+ return false;
5942
+ }
5864
5943
  const latestFrame = activeLiveStream.latestFrame;
5865
5944
  if (!latestFrame || latestFrame.currentGenerationVerified !== true) {
5866
5945
  return false;
@@ -5984,6 +6063,7 @@ export function createRemoteHub(options = {}) {
5984
6063
  lastFrameAt: '',
5985
6064
  lastFrameSeq: 0,
5986
6065
  framesReceived: 0,
6066
+ readyFrameReceived: false,
5987
6067
  streamPurpose,
5988
6068
  captureGeneration
5989
6069
  });
@@ -6201,8 +6281,9 @@ export function createRemoteHub(options = {}) {
6201
6281
  stoppedAt: '',
6202
6282
  stopReason: '',
6203
6283
  lastFrameAt: '',
6204
- lastFrameSeq: 0,
6284
+ lastFrameSeq: 0,
6205
6285
  framesReceived: 0,
6286
+ readyFrameReceived: false,
6206
6287
  streamPurpose,
6207
6288
  captureGeneration,
6208
6289
  latestFrame: null,
package/hub/src/server.js CHANGED
@@ -170,19 +170,27 @@ function readPositiveIntegerEnv(name, fallback) {
170
170
  function handleRemoteHubEvent(type, event) {
171
171
  liveDeskUpdateManager?.handleRemoteEvent(type, event);
172
172
  hubTransferJobs?.handleRemoteEvent(type, event);
173
- if (type === 'RemoteInputApplied') {
174
- for (const ws of inputClients) {
175
- sendJson(ws, {
176
- type: 'RemoteInputApplied',
177
- ...event
173
+ if (type === 'RemoteInputApplied') {
174
+ const targetConnectionId = String(event?.hubConnectionId || '').trim();
175
+ for (const ws of inputClients) {
176
+ if (targetConnectionId && ws.liveDeskInputClientId !== targetConnectionId) {
177
+ continue;
178
+ }
179
+ sendJson(ws, {
180
+ type: 'RemoteInputApplied',
181
+ ...event
178
182
  });
179
183
  }
180
184
  return;
181
- }
185
+ }
182
186
  if (type === 'RemoteInputError') {
183
- for (const ws of inputClients) {
184
- sendJson(ws, {
185
- type: 'RemoteInputError',
187
+ const targetConnectionId = String(event?.hubConnectionId || '').trim();
188
+ for (const ws of inputClients) {
189
+ if (targetConnectionId && ws.liveDeskInputClientId !== targetConnectionId) {
190
+ continue;
191
+ }
192
+ sendJson(ws, {
193
+ type: 'RemoteInputError',
186
194
  ...event
187
195
  });
188
196
  }
@@ -2045,8 +2053,13 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
2045
2053
  fps: Number(frame.fps || 0) || 0,
2046
2054
  requestedFps: Number(frame.requestedFps || 0) || 0,
2047
2055
  effectiveFps: Number(frame.effectiveFps || 0) || 0,
2048
- captureAverageMs: Number(frame.captureAverageMs || 0) || 0,
2049
- captureP95Ms: Number(frame.captureP95Ms || 0) || 0,
2056
+ captureTimingAvailable: frame.captureTimingAvailable === true,
2057
+ captureAverageMs: frame.captureTimingAvailable === true && Number.isFinite(Number(frame.captureAverageMs))
2058
+ ? Number(frame.captureAverageMs)
2059
+ : null,
2060
+ captureP95Ms: frame.captureTimingAvailable === true && Number.isFinite(Number(frame.captureP95Ms))
2061
+ ? Number(frame.captureP95Ms)
2062
+ : null,
2050
2063
  slowFrameCount: Number(frame.slowFrameCount || 0) || 0,
2051
2064
  captureHelperRestarts: Number(frame.captureHelperRestarts || 0) || 0,
2052
2065
  capturedAt: frame.capturedAt || '',
@@ -2055,8 +2068,12 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
2055
2068
  hubPacketEpochMs,
2056
2069
  byteLength: Number(frameEvent.byteLength || payload.length) || payload.length,
2057
2070
  contentHash: frame.contentHash || '',
2058
- captureMs: Number(frame.captureMs || 0) || 0,
2059
- captureStageMs: Number(frame.captureStageMs || 0) || 0,
2071
+ captureMs: frame.captureTimingAvailable === true && Number.isFinite(Number(frame.captureMs))
2072
+ ? Number(frame.captureMs)
2073
+ : null,
2074
+ captureStageMs: frame.captureTimingAvailable === true && Number.isFinite(Number(frame.captureStageMs))
2075
+ ? Number(frame.captureStageMs)
2076
+ : null,
2060
2077
  convertMs: Number(frame.convertMs || 0) || 0,
2061
2078
  compressMs: Number(frame.compressMs || 0) || 0,
2062
2079
  captureSourceSerial: Number(frame.captureSourceSerial || 0) || 0,
@@ -2157,7 +2174,8 @@ function broadcastRemoteBinaryFrame(frameEvent) {
2157
2174
  || Number(frame.monitorIndex || 0) !== expectedBinding.monitorIndex) {
2158
2175
  continue;
2159
2176
  }
2160
- if (!expectedBinding.readySent) {
2177
+ const requiresReadyKeyFrame = String(expectedBinding.streamPurpose || '').toLowerCase() === 'control' && isH264;
2178
+ if (!expectedBinding.readySent && (!requiresReadyKeyFrame || isKeyFrame)) {
2161
2179
  expectedBinding.readySent = sendJson(client, {
2162
2180
  type: 'RemoteFrameStreamReady',
2163
2181
  deviceId,
@@ -4214,11 +4232,14 @@ inputWss.on('connection', ws => {
4214
4232
  if (fireAndForget && result?.ok) {
4215
4233
  return;
4216
4234
  }
4217
- sendJson(ws, {
4218
- type: result?.ok ? 'RemoteInputQueued' : 'RemoteInputError',
4219
- requestId: payload?.requestId || '',
4220
- deviceId,
4221
- result,
4235
+ sendJson(ws, {
4236
+ type: result?.ok ? 'RemoteInputQueued' : 'RemoteInputError',
4237
+ requestId: payload?.requestId || '',
4238
+ deviceId,
4239
+ inputSeq: Number(input?.inputSeq || 0) || 0,
4240
+ inputEventId: String(input?.inputEventId || ''),
4241
+ hubConnectionId: ws.liveDeskInputClientId,
4242
+ result,
4222
4243
  timestamp: new Date().toISOString()
4223
4244
  });
4224
4245
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.431",
4
- "livedeskClientVersion": "0.1.189",
3
+ "version": "0.1.433",
4
+ "livedeskClientVersion": "0.1.191",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",
7
7
  "type": "module",
@@ -50,10 +50,10 @@
50
50
  "ws": "^8.18.3"
51
51
  },
52
52
  "optionalDependencies": {
53
- "@livedesk/fast-linux-x64": "0.1.396",
54
- "@livedesk/fast-osx-arm64": "0.1.396",
55
- "@livedesk/fast-osx-x64": "0.1.396",
56
- "@livedesk/fast-win-x64": "0.1.396"
53
+ "@livedesk/fast-linux-x64": "0.1.398",
54
+ "@livedesk/fast-osx-arm64": "0.1.398",
55
+ "@livedesk/fast-osx-x64": "0.1.398",
56
+ "@livedesk/fast-win-x64": "0.1.398"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public"