livedesk 0.1.498 → 0.1.500
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/bin/livedesk.js +3 -3
- package/bootstrap/legacy-client-update.js +2 -2
- package/bootstrap/role-bootstrap-server.js +16 -9
- package/client/bin/livedesk-client-node.js +65 -8
- package/client/package.json +5 -5
- package/client/src/runtime/client-runtime-server.js +10 -13
- package/hub/package.json +1 -1
- package/hub/src/live-desk-update.js +88 -39
- package/hub/src/remote-hub.js +61 -9
- package/package.json +6 -6
- package/web/dist/assets/index-9p6M3oJV.js +186 -0
- package/web/dist/index.html +1 -1
- package/web/dist/livedesk-build-evidence.json +11 -11
- package/web/dist/assets/index-VWc8JQDq.js +0 -186
package/bin/livedesk.js
CHANGED
|
@@ -2640,9 +2640,9 @@ async function runManager(args, resolvedRole = null, runtimeLock = null, updateH
|
|
|
2640
2640
|
);
|
|
2641
2641
|
const packageTarget = `livedesk@${requestedVersion}`;
|
|
2642
2642
|
const nextArgs = [
|
|
2643
|
-
'-y',
|
|
2644
|
-
'--prefer-
|
|
2645
|
-
'--prefix',
|
|
2643
|
+
'-y',
|
|
2644
|
+
'--prefer-offline',
|
|
2645
|
+
'--prefix',
|
|
2646
2646
|
neutralCwd,
|
|
2647
2647
|
'--workspaces=false',
|
|
2648
2648
|
packageTarget,
|
|
@@ -1019,7 +1019,7 @@ export function prefetchExactNpxRestore(config, env = process.env, options = {})
|
|
|
1019
1019
|
}
|
|
1020
1020
|
const neutralCwd = prepareNeutralCwd(config?.neutralCwd);
|
|
1021
1021
|
const invocation = resolveNpxInvocation(
|
|
1022
|
-
buildExactNpxArgs(neutralCwd, '--prefer-
|
|
1022
|
+
buildExactNpxArgs(neutralCwd, '--prefer-offline', version, ['--version']),
|
|
1023
1023
|
env
|
|
1024
1024
|
);
|
|
1025
1025
|
const result = (options.spawnSyncImpl || spawnSync)(
|
|
@@ -1691,7 +1691,7 @@ export async function runLegacyClientUpdateSupervisor(options = {}) {
|
|
|
1691
1691
|
config.targetProductVersion,
|
|
1692
1692
|
'target',
|
|
1693
1693
|
attempt,
|
|
1694
|
-
'--prefer-
|
|
1694
|
+
'--prefer-offline',
|
|
1695
1695
|
config.targetVersion
|
|
1696
1696
|
);
|
|
1697
1697
|
ownershipCheck();
|
|
@@ -31,23 +31,30 @@ function writePrivateJson(path, value) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function originAllowed(req, host, port) {
|
|
35
|
-
const origin = clean(req.headers.origin, 300);
|
|
34
|
+
function originAllowed(req, host, port) {
|
|
35
|
+
const origin = clean(req.headers.origin, 300);
|
|
36
36
|
const requestHost = clean(req.headers.host, 300).split(':')[0].toLowerCase();
|
|
37
37
|
const allowedHost = host.toLowerCase() === 'localhost' || host === '127.0.0.1' ? host.toLowerCase() : '127.0.0.1';
|
|
38
38
|
if (requestHost && requestHost !== allowedHost && requestHost !== 'localhost' && requestHost !== '127.0.0.1') return false;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
if (!origin) {
|
|
40
|
+
const fetchSite = clean(req.headers['sec-fetch-site'], 40).toLowerCase();
|
|
41
|
+
return !fetchSite
|
|
42
|
+
|| fetchSite === 'same-origin'
|
|
43
|
+
|| fetchSite === 'same-site'
|
|
44
|
+
|| fetchSite === 'none';
|
|
45
|
+
}
|
|
46
|
+
// Electron loads the bootstrap UI from the loopback HTTP origin. An opaque
|
|
47
|
+
// origin cannot distinguish that UI from an attacker-controlled local file
|
|
48
|
+
// or sandbox and therefore receives no bootstrap API privilege.
|
|
49
|
+
if (origin === 'null') return false;
|
|
43
50
|
return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
function sendJson(req, res, status, payload, port) {
|
|
47
54
|
const origin = clean(req.headers.origin, 300);
|
|
48
|
-
const allowedOrigin = origin ===
|
|
49
|
-
?
|
|
50
|
-
:
|
|
55
|
+
const allowedOrigin = origin === `http://localhost:${port}`
|
|
56
|
+
? origin
|
|
57
|
+
: `http://127.0.0.1:${port}`;
|
|
51
58
|
res.writeHead(status, {
|
|
52
59
|
'Content-Type': 'application/json; charset=utf-8',
|
|
53
60
|
'Cache-Control': 'no-store',
|
|
@@ -251,7 +251,7 @@ async function waitForClientUpdateShutdownSignal(
|
|
|
251
251
|
return { ready: false, error };
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
function markClientUpdateConnected() {
|
|
254
|
+
function markClientUpdateConnected() {
|
|
255
255
|
const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
|
|
256
256
|
if (!operationId) return;
|
|
257
257
|
void writeClientUpdateState('connected', {
|
|
@@ -262,8 +262,59 @@ function markClientUpdateConnected() {
|
|
|
262
262
|
error: ''
|
|
263
263
|
}).catch(error => {
|
|
264
264
|
console.error(`LiveDesk client update diagnostics could not be persisted: ${error?.message || error}`);
|
|
265
|
-
});
|
|
266
|
-
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function publishClientUpdateSupervisorProof(socket) {
|
|
269
|
+
const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
|
|
270
|
+
const attemptId = String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
|
|
271
|
+
const expectedLauncherPid = Number(process.env.LIVEDESK_CLIENT_PARENT_PID || 0);
|
|
272
|
+
if (!operationId || !attemptId || !Number.isInteger(expectedLauncherPid) || expectedLauncherPid <= 1) return;
|
|
273
|
+
const deadline = Date.now() + 60_000;
|
|
274
|
+
while (Date.now() < deadline && !socket.destroyed) {
|
|
275
|
+
try {
|
|
276
|
+
const state = JSON.parse(await fs.readFile(getClientUpdateStatePath(), 'utf8'));
|
|
277
|
+
if (String(state?.operationId || '') !== operationId
|
|
278
|
+
|| String(state?.attemptId || '') !== attemptId) {
|
|
279
|
+
await wait(100);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (['failed', 'restored', 'cancelled'].includes(String(state.stage || ''))) return;
|
|
283
|
+
const launcherPid = Number(state.launcherPid || 0);
|
|
284
|
+
const agentPid = Number(state.agentPid || 0);
|
|
285
|
+
const supervisorPid = Number(state.supervisorPid || 0);
|
|
286
|
+
const completedAt = String(state.completedAt || '').trim();
|
|
287
|
+
const exactSupervisorCompletion = state.stage === 'connected'
|
|
288
|
+
&& state.restartVerified === true
|
|
289
|
+
&& Number.isFinite(Date.parse(completedAt))
|
|
290
|
+
&& launcherPid === expectedLauncherPid
|
|
291
|
+
&& agentPid === process.pid
|
|
292
|
+
&& Number.isInteger(supervisorPid)
|
|
293
|
+
&& supervisorPid > 1
|
|
294
|
+
&& String(state.productVersion || '') === PRODUCT_VERSION
|
|
295
|
+
&& String(state.agentVersion || '') === AGENT_VERSION
|
|
296
|
+
&& String(state.targetProductVersion || '') === PRODUCT_VERSION
|
|
297
|
+
&& String(state.targetAgentVersion || state.targetVersion || '') === AGENT_VERSION;
|
|
298
|
+
if (exactSupervisorCompletion) {
|
|
299
|
+
writeJsonLine(socket, {
|
|
300
|
+
type: 'client.update.verified',
|
|
301
|
+
operationId,
|
|
302
|
+
attemptId,
|
|
303
|
+
launcherPid,
|
|
304
|
+
agentPid,
|
|
305
|
+
supervisorPid,
|
|
306
|
+
productVersion: PRODUCT_VERSION,
|
|
307
|
+
agentVersion: AGENT_VERSION,
|
|
308
|
+
completedAt
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
// The exact package supervisor may be replacing the atomic state file.
|
|
314
|
+
}
|
|
315
|
+
await wait(100);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
267
318
|
|
|
268
319
|
function printHelp() {
|
|
269
320
|
console.log(`
|
|
@@ -1972,9 +2023,10 @@ function connectOnce(options, deviceId) {
|
|
|
1972
2023
|
remoteFiles: true,
|
|
1973
2024
|
fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
|
|
1974
2025
|
computerAgent: options.taskEnabled,
|
|
1975
|
-
taskDispatch: options.taskEnabled,
|
|
1976
|
-
clientUpdate: true,
|
|
1977
|
-
|
|
2026
|
+
taskDispatch: options.taskEnabled,
|
|
2027
|
+
clientUpdate: true,
|
|
2028
|
+
clientUpdateVerifiedProof: true,
|
|
2029
|
+
productVersion: PRODUCT_VERSION,
|
|
1978
2030
|
agentApproval: options.taskEnabled,
|
|
1979
2031
|
agentAudit: options.taskEnabled,
|
|
1980
2032
|
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
@@ -2000,8 +2052,13 @@ function connectOnce(options, deviceId) {
|
|
|
2000
2052
|
options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
|
|
2001
2053
|
? message.effectivePolicy
|
|
2002
2054
|
: null;
|
|
2003
|
-
console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
|
|
2004
|
-
markClientUpdateConnected();
|
|
2055
|
+
console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
|
|
2056
|
+
markClientUpdateConnected();
|
|
2057
|
+
void publishClientUpdateSupervisorProof(socket).catch(error => {
|
|
2058
|
+
if (!socket.destroyed) {
|
|
2059
|
+
console.error(`LiveDesk client update verification proof failed: ${error?.message || error}`);
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2005
2062
|
writeJsonLine(socket, {
|
|
2006
2063
|
type: 'status',
|
|
2007
2064
|
status: getStatus(options)
|
package/client/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.239",
|
|
4
4
|
"description": "LiveDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"ws": "^8.18.3"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
47
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
48
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
45
|
+
"@livedesk/fast-linux-x64": "0.1.437",
|
|
46
|
+
"@livedesk/fast-osx-arm64": "0.1.437",
|
|
47
|
+
"@livedesk/fast-osx-x64": "0.1.437",
|
|
48
|
+
"@livedesk/fast-win-x64": "0.1.437"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
@@ -1022,10 +1022,9 @@ function isLoopbackRequest(req) {
|
|
|
1022
1022
|
return address === '127.0.0.1' || address === '::1' || address === 'localhost' || !address;
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
|
-
function isTrustedApiOrigin(value, trustedWebOrigins) {
|
|
1026
|
-
const rawOrigin = normalizeString(value, 300);
|
|
1027
|
-
|
|
1028
|
-
const origin = normalizeOrigin(rawOrigin);
|
|
1025
|
+
function isTrustedApiOrigin(value, trustedWebOrigins) {
|
|
1026
|
+
const rawOrigin = normalizeString(value, 300);
|
|
1027
|
+
const origin = normalizeOrigin(rawOrigin);
|
|
1029
1028
|
if (!origin) return false;
|
|
1030
1029
|
if (trustedWebOrigins.has(origin)) return true;
|
|
1031
1030
|
try {
|
|
@@ -1049,11 +1048,10 @@ function isTrustedLocalRequest(req, host, port, options = {}) {
|
|
|
1049
1048
|
const requestHost = normalizeString(req.headers.host, 300).split(':')[0].toLowerCase();
|
|
1050
1049
|
if (requestHost && requestHost !== host.toLowerCase() && requestHost !== 'localhost' && requestHost !== '127.0.0.1') return false;
|
|
1051
1050
|
|
|
1052
|
-
// Chromium can mark modulepreload/stylesheet requests as cross-site
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1056
|
-
// remain protected by the CSRF token check below.
|
|
1051
|
+
// Chromium can mark modulepreload/stylesheet requests as cross-site. Static
|
|
1052
|
+
// assets are not privileged. An opaque Origin (`null`) is never accepted for
|
|
1053
|
+
// an API route because it cannot distinguish the LiveDesk shell from an
|
|
1054
|
+
// attacker-controlled file, sandbox, or data page.
|
|
1057
1055
|
if (options.allowStaticCrossSite === true) {
|
|
1058
1056
|
return true;
|
|
1059
1057
|
}
|
|
@@ -1073,10 +1071,9 @@ function isTrustedLocalRequest(req, host, port, options = {}) {
|
|
|
1073
1071
|
|| fetchSite === 'none';
|
|
1074
1072
|
}
|
|
1075
1073
|
|
|
1076
|
-
function resolveCorsOrigin(requestOrigin, port, trustedWebOrigins) {
|
|
1077
|
-
const origin = normalizeString(requestOrigin, 300);
|
|
1078
|
-
|
|
1079
|
-
return origin && isTrustedApiOrigin(origin, trustedWebOrigins)
|
|
1074
|
+
function resolveCorsOrigin(requestOrigin, port, trustedWebOrigins) {
|
|
1075
|
+
const origin = normalizeString(requestOrigin, 300);
|
|
1076
|
+
return origin && isTrustedApiOrigin(origin, trustedWebOrigins)
|
|
1080
1077
|
? normalizeOrigin(origin)
|
|
1081
1078
|
: `http://127.0.0.1:${port}`;
|
|
1082
1079
|
}
|
package/hub/package.json
CHANGED
|
@@ -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
|
|
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.
|
|
231
|
-
target.
|
|
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
|
-
|
|
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
|
|
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.
|
|
562
|
-
target.
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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'
|
|
748
|
+
if (type === 'RemoteDeviceConnected'
|
|
749
|
+
|| type === 'RemoteDeviceDisconnected'
|
|
750
|
+
|| type === 'RemoteClientUpdateVerified') {
|
|
702
751
|
verifyTargets();
|
|
703
752
|
return;
|
|
704
753
|
}
|
package/hub/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
|
|
|
@@ -1123,6 +1123,42 @@ function safeText(value, maxLength = 1000) {
|
|
|
1123
1123
|
return String(value ?? '').replace(/\0/g, '').trim().slice(0, maxLength);
|
|
1124
1124
|
}
|
|
1125
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
|
+
|
|
1126
1162
|
function normalizeRemoteKeyboardKey(value) {
|
|
1127
1163
|
const raw = String(value ?? '').replace(/\0/g, '');
|
|
1128
1164
|
return raw === ' ' ? ' ' : safeString(raw, 80);
|
|
@@ -1626,8 +1662,9 @@ function serializeDevice(device, options = {}) {
|
|
|
1626
1662
|
byteLength: device.latestAudioFrame.byteLength
|
|
1627
1663
|
}
|
|
1628
1664
|
: null,
|
|
1629
|
-
latestAudioStatus: device.latestAudioStatus ? { ...device.latestAudioStatus } : null,
|
|
1630
|
-
|
|
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 },
|
|
1631
1668
|
latestTask: device.latestTask ? { ...device.latestTask } : null,
|
|
1632
1669
|
synthetic: device.synthetic === true,
|
|
1633
1670
|
recentTasks: Array.isArray(device.recentTasks)
|
|
@@ -4427,8 +4464,9 @@ export function createRemoteHub(options = {}) {
|
|
|
4427
4464
|
liveCapturePause: existing?.liveCapturePause || null,
|
|
4428
4465
|
activeAudioStream: null,
|
|
4429
4466
|
latestAudioFrame: null,
|
|
4430
|
-
latestAudioStatus: null,
|
|
4431
|
-
|
|
4467
|
+
latestAudioStatus: null,
|
|
4468
|
+
clientUpdateProof: null,
|
|
4469
|
+
udp: {
|
|
4432
4470
|
enabled: capabilities.udpP2p === true,
|
|
4433
4471
|
state: 'tcp-only',
|
|
4434
4472
|
ready: false,
|
|
@@ -7441,7 +7479,7 @@ export function createRemoteHub(options = {}) {
|
|
|
7441
7479
|
device.counters.statusReceived += 1;
|
|
7442
7480
|
emitRemoteEvent('RemoteDeviceStatus', device);
|
|
7443
7481
|
break;
|
|
7444
|
-
case 'command.result':
|
|
7482
|
+
case 'command.result':
|
|
7445
7483
|
case 'command.error':
|
|
7446
7484
|
device.counters.commandResultsReceived += 1;
|
|
7447
7485
|
{
|
|
@@ -7469,9 +7507,23 @@ export function createRemoteHub(options = {}) {
|
|
|
7469
7507
|
commandId: safeString(message.commandId, 128),
|
|
7470
7508
|
result: message.result ?? null,
|
|
7471
7509
|
error: safeString(message.error, 500)
|
|
7472
|
-
});
|
|
7473
|
-
break;
|
|
7474
|
-
case '
|
|
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':
|
|
7475
7527
|
case 'input.error':
|
|
7476
7528
|
handleRemoteInputOutcome(device, message, 'main');
|
|
7477
7529
|
break;
|
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.500",
|
|
4
|
+
"livedeskClientVersion": "0.1.239",
|
|
5
5
|
"buildFlavor": "production",
|
|
6
6
|
"description": "LiveDesk Hub and client launcher",
|
|
7
7
|
"type": "module",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"ws": "^8.18.3"
|
|
53
53
|
},
|
|
54
54
|
"optionalDependencies": {
|
|
55
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
56
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
57
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
58
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
55
|
+
"@livedesk/fast-linux-x64": "0.1.437",
|
|
56
|
+
"@livedesk/fast-osx-arm64": "0.1.437",
|
|
57
|
+
"@livedesk/fast-osx-x64": "0.1.437",
|
|
58
|
+
"@livedesk/fast-win-x64": "0.1.437"
|
|
59
59
|
},
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"access": "public"
|