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.
- package/client/package.json +5 -5
- package/client/src/runtime/client-runtime-server.js +32 -2
- package/hub/src/remote-hub.js +117 -36
- package/hub/src/server.js +40 -19
- package/package.json +6 -6
- package/web/dist/assets/index-BUnTKnuj.js +25 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-B959xLYx.js +0 -25
package/client/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
45
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
47
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
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
|
|
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
|
-
|
|
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;
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -685,7 +685,9 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
685
685
|
const captureGeneration = Number(input.captureGeneration ?? input.CaptureGeneration);
|
|
686
686
|
return {
|
|
687
687
|
type,
|
|
688
|
-
|
|
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
|
-
|
|
4043
|
-
|
|
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:
|
|
4052
|
-
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 (
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
2049
|
-
|
|
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
|
|
2059
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"livedeskClientVersion": "0.1.
|
|
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.
|
|
54
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
55
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
56
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
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"
|