@livedesk/client 0.1.169 → 0.1.171
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-client-node.js +280 -87
- package/bin/livedesk-client.js +121 -19
- package/package.json +5 -5
|
@@ -9,14 +9,15 @@ import { spawn } from 'child_process';
|
|
|
9
9
|
import { createRequire } from 'node:module';
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
|
-
const AGENT_VERSION = (() => {
|
|
12
|
+
const AGENT_VERSION = (() => {
|
|
13
13
|
try {
|
|
14
14
|
return String(require('../package.json').version || '0.0.0');
|
|
15
15
|
} catch {
|
|
16
16
|
return '0.0.0';
|
|
17
|
-
}
|
|
18
|
-
})();
|
|
19
|
-
const
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
const PRODUCT_VERSION = String(process.env.LIVEDESK_NPM_LAUNCHER_VERSION || AGENT_VERSION).trim() || AGENT_VERSION;
|
|
20
|
+
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
20
21
|
const DEFAULT_HEARTBEAT_MS = 5000;
|
|
21
22
|
const DEFAULT_RECONNECT_MS = 5000;
|
|
22
23
|
const EXIT_INVALID_PAIR_TOKEN = 23;
|
|
@@ -29,7 +30,133 @@ const MAX_AI_OUTPUT_CHARS = 6000;
|
|
|
29
30
|
const MAX_FILE_TRANSFER_FILES = 24;
|
|
30
31
|
const MAX_FILE_TRANSFER_BYTES = 24 * 1024 * 1024;
|
|
31
32
|
const MAX_AGENT_OUTPUT_CHARS = 32000;
|
|
32
|
-
const launchedProcessRegistry = new Map();
|
|
33
|
+
const launchedProcessRegistry = new Map();
|
|
34
|
+
const CLIENT_UPDATE_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
35
|
+
|
|
36
|
+
function normalizeClientUpdateTargetVersion(value) {
|
|
37
|
+
const version = String(value || '').trim().replace(/^v/i, '');
|
|
38
|
+
if (!version) return '';
|
|
39
|
+
if (!CLIENT_UPDATE_VERSION_PATTERN.test(version)) {
|
|
40
|
+
throw new Error(`Invalid LiveDesk product update version: ${version}`);
|
|
41
|
+
}
|
|
42
|
+
return version;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getClientUpdateStatePath() {
|
|
46
|
+
return String(process.env.LIVEDESK_CLIENT_UPDATE_STATE_PATH || path.join(os.homedir(), '.livedesk', 'client-update.json'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function writeClientUpdateState(stage, details = {}) {
|
|
50
|
+
const statePath = getClientUpdateStatePath();
|
|
51
|
+
const operationId = String(details.operationId || process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
|
|
52
|
+
if (!operationId) return;
|
|
53
|
+
const attemptId = stage === 'scheduled'
|
|
54
|
+
? ''
|
|
55
|
+
: String(details.attemptId || process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
|
|
56
|
+
let previous = null;
|
|
57
|
+
try {
|
|
58
|
+
previous = JSON.parse(await fs.readFile(statePath, 'utf8'));
|
|
59
|
+
} catch {
|
|
60
|
+
// The first update stage creates the file.
|
|
61
|
+
}
|
|
62
|
+
const replacesTerminalOperation = previous?.operationId
|
|
63
|
+
&& previous.operationId !== operationId
|
|
64
|
+
&& stage === 'scheduled'
|
|
65
|
+
&& ['connected', 'failed', 'restored'].includes(String(previous.stage || ''));
|
|
66
|
+
if (previous?.operationId && previous.operationId !== operationId && !replacesTerminalOperation) return;
|
|
67
|
+
if (!replacesTerminalOperation
|
|
68
|
+
&& previous?.attemptId
|
|
69
|
+
&& attemptId
|
|
70
|
+
&& previous.attemptId !== attemptId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!replacesTerminalOperation
|
|
74
|
+
&& previous?.stage === 'connected'
|
|
75
|
+
&& previous?.restartVerified === true
|
|
76
|
+
&& stage !== 'connected') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const targetProductVersion = String(
|
|
80
|
+
details.targetProductVersion
|
|
81
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION
|
|
82
|
+
|| (!replacesTerminalOperation ? previous?.targetProductVersion : '')
|
|
83
|
+
|| ''
|
|
84
|
+
).trim();
|
|
85
|
+
const targetAgentVersion = String(
|
|
86
|
+
details.targetAgentVersion
|
|
87
|
+
|| details.targetVersion
|
|
88
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_AGENT_VERSION
|
|
89
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION
|
|
90
|
+
|| (!replacesTerminalOperation ? previous?.targetAgentVersion : '')
|
|
91
|
+
|| (!replacesTerminalOperation ? previous?.targetVersion : '')
|
|
92
|
+
|| ''
|
|
93
|
+
).trim();
|
|
94
|
+
const state = {
|
|
95
|
+
...(!replacesTerminalOperation && previous && typeof previous === 'object' ? previous : {}),
|
|
96
|
+
operationId,
|
|
97
|
+
stage,
|
|
98
|
+
targetVersion: targetAgentVersion,
|
|
99
|
+
targetProductVersion,
|
|
100
|
+
targetAgentVersion,
|
|
101
|
+
productVersion: PRODUCT_VERSION,
|
|
102
|
+
agentVersion: AGENT_VERSION,
|
|
103
|
+
agentPid: process.pid,
|
|
104
|
+
launcherPid: Number(process.env.LIVEDESK_CLIENT_PARENT_PID || 0) || 0,
|
|
105
|
+
attemptId,
|
|
106
|
+
updatedAt: new Date().toISOString(),
|
|
107
|
+
...details
|
|
108
|
+
};
|
|
109
|
+
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
|
110
|
+
const temporaryPath = `${statePath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
111
|
+
try {
|
|
112
|
+
await fs.writeFile(temporaryPath, JSON.stringify(state, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
113
|
+
await fs.rename(temporaryPath, statePath);
|
|
114
|
+
} finally {
|
|
115
|
+
await fs.rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function waitForClientUpdateShutdownSignal(operationId, timeoutMs = 150_000) {
|
|
120
|
+
const statePath = getClientUpdateStatePath();
|
|
121
|
+
const deadline = Date.now() + timeoutMs;
|
|
122
|
+
while (Date.now() < deadline) {
|
|
123
|
+
try {
|
|
124
|
+
const state = JSON.parse(await fs.readFile(statePath, 'utf8'));
|
|
125
|
+
if (state?.operationId !== operationId) {
|
|
126
|
+
await wait(200);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (state.stage === 'preflight-ready' || state.stage === 'waiting-for-shutdown') return true;
|
|
130
|
+
if (state.stage === 'failed') {
|
|
131
|
+
console.error(`LiveDesk client update preparation failed: ${state.error || 'unknown error'}`);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// The detached supervisor may still be writing its first stage.
|
|
136
|
+
}
|
|
137
|
+
await wait(200);
|
|
138
|
+
}
|
|
139
|
+
await writeClientUpdateState('failed', {
|
|
140
|
+
operationId,
|
|
141
|
+
error: 'Timed out waiting for the LiveDesk update supervisor to prepare the exact package.'
|
|
142
|
+
}).catch(() => undefined);
|
|
143
|
+
console.error('LiveDesk client update preparation timed out; the current Agent will remain running.');
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function markClientUpdateConnected() {
|
|
148
|
+
const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
|
|
149
|
+
if (!operationId) return;
|
|
150
|
+
void writeClientUpdateState('connected', {
|
|
151
|
+
operationId,
|
|
152
|
+
attemptId: String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim(),
|
|
153
|
+
connectedAt: new Date().toISOString(),
|
|
154
|
+
restartVerified: true,
|
|
155
|
+
error: ''
|
|
156
|
+
}).catch(error => {
|
|
157
|
+
console.error(`LiveDesk client update diagnostics could not be persisted: ${error?.message || error}`);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
33
160
|
|
|
34
161
|
function printHelp() {
|
|
35
162
|
console.log(`
|
|
@@ -1318,76 +1445,137 @@ function remotePolicyAllows(options, command) {
|
|
|
1318
1445
|
return denied ? { ok: false, error: `${denied}-blocked-by-settings` } : { ok: true };
|
|
1319
1446
|
}
|
|
1320
1447
|
|
|
1321
|
-
function buildClientUpdateBootstrapScript() {
|
|
1322
|
-
return [
|
|
1323
|
-
'const { spawn } = require("node:child_process");',
|
|
1448
|
+
function buildClientUpdateBootstrapScript() {
|
|
1449
|
+
return [
|
|
1450
|
+
'const { spawn } = require("node:child_process");',
|
|
1324
1451
|
'const fs = require("node:fs");',
|
|
1325
1452
|
'const path = require("node:path");',
|
|
1326
|
-
'const
|
|
1327
|
-
'const
|
|
1328
|
-
'const
|
|
1329
|
-
'const
|
|
1330
|
-
'const
|
|
1331
|
-
'const
|
|
1332
|
-
'const
|
|
1333
|
-
'const
|
|
1334
|
-
'if (!
|
|
1335
|
-
'const
|
|
1336
|
-
'const
|
|
1337
|
-
'const npxCli =
|
|
1338
|
-
'const
|
|
1339
|
-
'const
|
|
1340
|
-
'const
|
|
1341
|
-
'process.env.
|
|
1342
|
-
'(
|
|
1343
|
-
''
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1453
|
+
'const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || "").trim();',
|
|
1454
|
+
'const statePath = String(process.env.LIVEDESK_CLIENT_UPDATE_STATE_PATH || path.join(require("node:os").homedir(), ".livedesk", "client-update.json"));',
|
|
1455
|
+
'const cleanVersion = value => String(value || "").trim().replace(/^v/i, "");',
|
|
1456
|
+
'const targetProductVersion = cleanVersion(process.env.LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION || process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION);',
|
|
1457
|
+
'const versionPattern = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/;',
|
|
1458
|
+
'const readState = () => { try { return JSON.parse(fs.readFileSync(statePath, "utf8")); } catch { return {}; } };',
|
|
1459
|
+
'const writeFailure = error => { try { fs.mkdirSync(path.dirname(statePath), { recursive: true }); const previous = readState(); if (previous.operationId && previous.operationId !== operationId) return; const now = new Date().toISOString(); const next = { ...previous, operationId, stage: "failed", targetProductVersion, restartVerified: false, error: String(error?.message || error).slice(0, 4000), failedAt: now, updatedAt: now }; const temporary = statePath + "." + process.pid + ".handoff.tmp"; fs.writeFileSync(temporary, JSON.stringify(next, null, 2), { encoding: "utf8", mode: 0o600 }); fs.renameSync(temporary, statePath); } catch {} process.stderr.write("LiveDesk update handoff failed: " + (error?.message || error) + "\\n"); process.exitCode = 1; };',
|
|
1460
|
+
'const monitorPreflight = child => { let settled = false; const finish = (error, preserveFailure = false) => { if (settled) return; settled = true; clearInterval(poll); clearTimeout(timeout); child.unref(); if (preserveFailure) process.exitCode = 1; else if (error) writeFailure(error); }; const inspect = () => { const state = readState(); if (state.operationId !== operationId) return; if (state.stage === "preflight-ready" || state.stage === "waiting-for-shutdown") finish(); else if (state.stage === "failed") finish(null, true); }; const poll = setInterval(inspect, 100); const timeout = setTimeout(() => { try { child.kill(); } catch {} finish(new Error("Timed out waiting for exact-package update preflight.")); }, Math.max(1000, Number(process.env.LIVEDESK_CLIENT_UPDATE_HANDOFF_TIMEOUT_MS || 140000))); child.once("exit", (code, signal) => { inspect(); if (!settled) finish(new Error("Exact-package update supervisor exited before preflight (code=" + (code ?? "none") + ", signal=" + (signal || "none") + ").")); }); inspect(); };',
|
|
1461
|
+
'if (!operationId || !versionPattern.test(targetProductVersion)) { writeFailure(new Error("Invalid LiveDesk exact-package update handoff.")); } else {',
|
|
1462
|
+
'const env = { ...process.env, LIVEDESK_UPDATE_STARTER_PID: String(process.pid) };',
|
|
1463
|
+
'const nodeDir = path.dirname(process.execPath);',
|
|
1464
|
+
'const npxCli = [env.LIVEDESK_NPX_CLI_PATH, env.npm_execpath ? path.join(path.dirname(env.npm_execpath), "npx-cli.js") : "", env.LIVEDESK_NPX_EXECUTABLE ? path.join(path.dirname(env.LIVEDESK_NPX_EXECUTABLE), "node_modules", "npm", "bin", "npx-cli.js") : "", path.join(nodeDir, "node_modules", "npm", "bin", "npx-cli.js")].find(value => value && fs.existsSync(value));',
|
|
1465
|
+
'const npx = env.LIVEDESK_NPX_EXECUTABLE || (process.platform === "win32" ? "npx.cmd" : "npx");',
|
|
1466
|
+
'const args = ["-y", "--prefer-online", "livedesk@" + targetProductVersion, "--internal-legacy-client-update"];',
|
|
1467
|
+
'const quoteCmd = value => "\\"" + String(value).replaceAll("%", "%%").replaceAll("\\"", "\\"\\"") + "\\"";',
|
|
1468
|
+
'const invocation = npxCli ? { command: process.execPath, args: [npxCli, ...args] } : process.platform === "win32" ? { command: env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", "call " + quoteCmd(npx) + " " + args.map(quoteCmd).join(" ")] } : { command: npx, args };',
|
|
1469
|
+
'if (!npxCli && path.isAbsolute(npx) && !fs.existsSync(npx)) { writeFailure(new Error("LiveDesk npx executable was not found: " + npx)); } else { try { const child = spawn(invocation.command, invocation.args, { env, detached: true, stdio: "ignore", windowsHide: true }); child.once("error", writeFailure); child.once("spawn", () => monitorPreflight(child)); } catch (error) { writeFailure(error); } }',
|
|
1470
|
+
'}',
|
|
1471
|
+
''
|
|
1472
|
+
].join('\n');
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
async function scheduleClientUpdate(options, payload = {}) {
|
|
1476
|
+
const operationId = String(payload.operationId || crypto.randomUUID()).trim();
|
|
1477
|
+
const statePath = getClientUpdateStatePath();
|
|
1478
|
+
let targetProductVersion = '';
|
|
1479
|
+
let targetAgentVersion = '';
|
|
1480
|
+
try {
|
|
1481
|
+
targetProductVersion = normalizeClientUpdateTargetVersion(
|
|
1482
|
+
payload.targetProductVersion
|
|
1483
|
+
|| payload.productVersion
|
|
1484
|
+
|| payload.managerVersion
|
|
1485
|
+
|| payload.targetVersion
|
|
1486
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION
|
|
1487
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION
|
|
1488
|
+
|| ''
|
|
1489
|
+
);
|
|
1490
|
+
targetAgentVersion = normalizeClientUpdateTargetVersion(
|
|
1491
|
+
payload.targetAgentVersion
|
|
1492
|
+
|| payload.clientVersion
|
|
1493
|
+
|| payload.targetVersion
|
|
1494
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_AGENT_VERSION
|
|
1495
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION
|
|
1496
|
+
|| targetProductVersion
|
|
1497
|
+
|| ''
|
|
1498
|
+
);
|
|
1499
|
+
await writeClientUpdateState('scheduled', {
|
|
1500
|
+
operationId,
|
|
1501
|
+
targetVersion: targetAgentVersion,
|
|
1502
|
+
targetProductVersion,
|
|
1503
|
+
targetAgentVersion,
|
|
1504
|
+
requestedAt: new Date().toISOString(),
|
|
1505
|
+
restartVerified: false,
|
|
1506
|
+
error: ''
|
|
1507
|
+
});
|
|
1508
|
+
const configuredParentPid = Number(process.env.LIVEDESK_CLIENT_PARENT_PID || process.ppid);
|
|
1509
|
+
const parentPid = Number.isInteger(configuredParentPid) && configuredParentPid > 1
|
|
1510
|
+
? configuredParentPid
|
|
1511
|
+
: process.pid;
|
|
1512
|
+
if (!Number.isInteger(parentPid) || parentPid <= 1) {
|
|
1513
|
+
throw new Error('LiveDesk client launcher process id is unavailable.');
|
|
1514
|
+
}
|
|
1515
|
+
const nodeBinDir = path.dirname(process.execPath);
|
|
1516
|
+
const npxCandidates = process.platform === 'win32'
|
|
1517
|
+
? [process.env.LIVEDESK_NPX_EXECUTABLE, path.join(nodeBinDir, 'npx.cmd'), path.join(nodeBinDir, 'npx.exe'), 'npx.cmd']
|
|
1518
|
+
: [process.env.LIVEDESK_NPX_EXECUTABLE, path.join(nodeBinDir, 'npx'), 'npx'];
|
|
1519
|
+
const command = npxCandidates.find(candidate => candidate && (!path.isAbsolute(candidate) || existsSync(candidate)));
|
|
1520
|
+
if (!command) {
|
|
1521
|
+
throw new Error('LiveDesk npx executable was not found.');
|
|
1522
|
+
}
|
|
1523
|
+
return await new Promise((resolve, reject) => {
|
|
1524
|
+
const child = spawn(process.execPath, ['-e', buildClientUpdateBootstrapScript()], {
|
|
1525
|
+
env: {
|
|
1526
|
+
...process.env,
|
|
1527
|
+
LIVEDESK_NODE_EXECUTABLE: process.execPath,
|
|
1528
|
+
LIVEDESK_NPX_EXECUTABLE: command,
|
|
1529
|
+
LIVEDESK_UPDATE_WAIT_PID: String(parentPid),
|
|
1530
|
+
LIVEDESK_UPDATE_AGENT_PID: String(process.pid),
|
|
1531
|
+
LIVEDESK_SKIP_BROWSER_OPEN: '1',
|
|
1532
|
+
LIVEDESK_CLIENT_UPDATE_TARGET_VERSION: targetAgentVersion,
|
|
1533
|
+
LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION: targetProductVersion,
|
|
1534
|
+
LIVEDESK_CLIENT_UPDATE_TARGET_AGENT_VERSION: targetAgentVersion,
|
|
1535
|
+
LIVEDESK_CLIENT_UPDATE_CURRENT_PRODUCT_VERSION: PRODUCT_VERSION,
|
|
1536
|
+
LIVEDESK_CLIENT_UPDATE_CURRENT_AGENT_VERSION: AGENT_VERSION,
|
|
1537
|
+
LIVEDESK_CLIENT_UPDATE_LOCAL_LAUNCHER: String(
|
|
1538
|
+
process.env.LIVEDESK_UNIFIED_LAUNCHER_ENTRY
|
|
1539
|
+
|| process.env.LIVEDESK_CLIENT_UPDATE_LOCAL_LAUNCHER
|
|
1540
|
+
|| ''
|
|
1541
|
+
),
|
|
1542
|
+
LIVEDESK_CLIENT_UPDATE_OPERATION_ID: operationId,
|
|
1543
|
+
LIVEDESK_CLIENT_UPDATE_STATE_PATH: statePath,
|
|
1544
|
+
LIVEDESK_CLIENT_UPDATE_CWD: process.cwd()
|
|
1545
|
+
},
|
|
1546
|
+
detached: true,
|
|
1547
|
+
stdio: 'ignore',
|
|
1548
|
+
windowsHide: true
|
|
1549
|
+
});
|
|
1550
|
+
child.once('error', reject);
|
|
1551
|
+
child.once('spawn', () => {
|
|
1552
|
+
child.unref();
|
|
1553
|
+
resolve({
|
|
1554
|
+
ok: true,
|
|
1555
|
+
status: 'update-scheduled',
|
|
1556
|
+
targetVersion: targetAgentVersion,
|
|
1557
|
+
targetProductVersion,
|
|
1558
|
+
targetAgentVersion,
|
|
1559
|
+
operationId,
|
|
1560
|
+
statePath,
|
|
1561
|
+
parentPid,
|
|
1562
|
+
restartAfterMs: 0
|
|
1563
|
+
});
|
|
1564
|
+
});
|
|
1565
|
+
});
|
|
1566
|
+
} catch (error) {
|
|
1567
|
+
await writeClientUpdateState('failed', {
|
|
1568
|
+
operationId,
|
|
1569
|
+
targetVersion: targetAgentVersion,
|
|
1570
|
+
targetProductVersion,
|
|
1571
|
+
targetAgentVersion,
|
|
1572
|
+
error: error?.message || String(error),
|
|
1573
|
+
failedAt: new Date().toISOString(),
|
|
1574
|
+
restartVerified: false
|
|
1575
|
+
}).catch(() => undefined);
|
|
1576
|
+
throw error;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1391
1579
|
|
|
1392
1580
|
async function handleRemoteCommand(socket, options, message, nextFrameSeq, activeStreams) {
|
|
1393
1581
|
const command = String(message.command || '');
|
|
@@ -1414,16 +1602,18 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
|
|
|
1414
1602
|
return;
|
|
1415
1603
|
}
|
|
1416
1604
|
|
|
1417
|
-
if (command === 'livedesk.client-update') {
|
|
1418
|
-
try {
|
|
1419
|
-
const result = await scheduleClientUpdate(options, message.payload || {});
|
|
1605
|
+
if (command === 'livedesk.client-update') {
|
|
1606
|
+
try {
|
|
1607
|
+
const result = await scheduleClientUpdate(options, message.payload || {});
|
|
1420
1608
|
writeJsonLine(socket, {
|
|
1421
1609
|
type: 'command.result',
|
|
1422
|
-
commandId: message.commandId,
|
|
1423
|
-
result
|
|
1424
|
-
});
|
|
1425
|
-
|
|
1426
|
-
|
|
1610
|
+
commandId: message.commandId,
|
|
1611
|
+
result
|
|
1612
|
+
});
|
|
1613
|
+
if (await waitForClientUpdateShutdownSignal(result.operationId)) {
|
|
1614
|
+
process.exit(EXIT_CLIENT_UPDATE);
|
|
1615
|
+
}
|
|
1616
|
+
} catch (error) {
|
|
1427
1617
|
writeJsonLine(socket, {
|
|
1428
1618
|
type: 'command.result',
|
|
1429
1619
|
commandId: message.commandId,
|
|
@@ -1751,9 +1941,10 @@ function connectOnce(options, deviceId) {
|
|
|
1751
1941
|
hostname: os.hostname(),
|
|
1752
1942
|
platform: os.platform(),
|
|
1753
1943
|
arch: os.arch(),
|
|
1754
|
-
pid: process.pid,
|
|
1755
|
-
agentVersion: AGENT_VERSION,
|
|
1756
|
-
|
|
1944
|
+
pid: process.pid,
|
|
1945
|
+
agentVersion: AGENT_VERSION,
|
|
1946
|
+
productVersion: PRODUCT_VERSION,
|
|
1947
|
+
capabilities: {
|
|
1757
1948
|
status: true,
|
|
1758
1949
|
thumbnail: options.thumbnailEnabled,
|
|
1759
1950
|
liveStream: options.liveEnabled,
|
|
@@ -1768,7 +1959,8 @@ function connectOnce(options, deviceId) {
|
|
|
1768
1959
|
fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
|
|
1769
1960
|
computerAgent: options.taskEnabled,
|
|
1770
1961
|
taskDispatch: options.taskEnabled,
|
|
1771
|
-
clientUpdate: true,
|
|
1962
|
+
clientUpdate: true,
|
|
1963
|
+
productVersion: PRODUCT_VERSION,
|
|
1772
1964
|
agentApproval: options.taskEnabled,
|
|
1773
1965
|
agentAudit: options.taskEnabled,
|
|
1774
1966
|
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
@@ -1793,11 +1985,12 @@ function connectOnce(options, deviceId) {
|
|
|
1793
1985
|
}
|
|
1794
1986
|
|
|
1795
1987
|
const message = JSON.parse(line);
|
|
1796
|
-
if (message.type === 'welcome') {
|
|
1988
|
+
if (message.type === 'welcome') {
|
|
1797
1989
|
options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
|
|
1798
1990
|
? message.effectivePolicy
|
|
1799
1991
|
: null;
|
|
1800
|
-
console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
|
|
1992
|
+
console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
|
|
1993
|
+
markClientUpdateConnected();
|
|
1801
1994
|
writeJsonLine(socket, {
|
|
1802
1995
|
type: 'status',
|
|
1803
1996
|
status: getStatus(options)
|
package/bin/livedesk-client.js
CHANGED
|
@@ -46,6 +46,8 @@ const CLIENT_AUTH_STORAGE_KEY = 'livedesk.client.supabase.auth';
|
|
|
46
46
|
const CLIENT_HUB_TARGET_CACHE_STORAGE_KEY = 'livedesk.client.last-hub-target';
|
|
47
47
|
const DEVICE_ROLE_CACHE_PATH = join(UNIFIED_CLIENT_STATE_DIR, 'device-role.json');
|
|
48
48
|
const FAST_PREFLIGHT_CACHE_PATH = join(UNIFIED_CLIENT_STATE_DIR, 'fast-preflight.json');
|
|
49
|
+
const CLIENT_UPDATE_STATE_PATH = process.env.LIVEDESK_CLIENT_UPDATE_STATE_PATH
|
|
50
|
+
|| join(UNIFIED_CLIENT_STATE_DIR, 'client-update.json');
|
|
49
51
|
let activeAgentProcess = null;
|
|
50
52
|
let roleRestartRequest = null;
|
|
51
53
|
let discoveryWakeController = new AbortController();
|
|
@@ -377,7 +379,7 @@ function upsertForwardedOption(args, flag, value) {
|
|
|
377
379
|
return next;
|
|
378
380
|
}
|
|
379
381
|
|
|
380
|
-
function appendForwardedFlag(args, flag) {
|
|
382
|
+
function appendForwardedFlag(args, flag) {
|
|
381
383
|
if (args.includes(flag)) {
|
|
382
384
|
return args;
|
|
383
385
|
}
|
|
@@ -550,6 +552,91 @@ function createFileStorage(filePath) {
|
|
|
550
552
|
};
|
|
551
553
|
}
|
|
552
554
|
|
|
555
|
+
const CLIENT_UPDATE_SINGLE_FLAGS = new Set([
|
|
556
|
+
'--help',
|
|
557
|
+
'-h',
|
|
558
|
+
'--version',
|
|
559
|
+
'--login',
|
|
560
|
+
'--logout',
|
|
561
|
+
'--no-login',
|
|
562
|
+
'--startup-run',
|
|
563
|
+
'--no-open',
|
|
564
|
+
'--exit-on-disconnect',
|
|
565
|
+
'--exit-on-invalid-pair',
|
|
566
|
+
'--once'
|
|
567
|
+
]);
|
|
568
|
+
const CLIENT_UPDATE_VALUE_FLAGS = new Set([
|
|
569
|
+
'--update-wait-pid',
|
|
570
|
+
// Pairing credentials stay in the inherited private environment and must
|
|
571
|
+
// never be copied onto a replacement process command line.
|
|
572
|
+
'--pair'
|
|
573
|
+
]);
|
|
574
|
+
|
|
575
|
+
export function buildClientUpdateRestartArgs(prepared = {}) {
|
|
576
|
+
const source = Array.isArray(prepared.forwarded) ? prepared.forwarded : [];
|
|
577
|
+
const args = [];
|
|
578
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
579
|
+
const arg = String(source[index] || '');
|
|
580
|
+
if (!arg || arg === 'connect' || CLIENT_UPDATE_SINGLE_FLAGS.has(arg)) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (arg.startsWith('--pair=')) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (CLIENT_UPDATE_VALUE_FLAGS.has(arg)) {
|
|
587
|
+
index += 1;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
args.push(arg);
|
|
591
|
+
}
|
|
592
|
+
return args;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function readClientUpdateState() {
|
|
596
|
+
try {
|
|
597
|
+
return JSON.parse(readFileSync(CLIENT_UPDATE_STATE_PATH, 'utf8'));
|
|
598
|
+
} catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function writeClientUpdateRuntimeStage(stage, details = {}) {
|
|
604
|
+
const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
|
|
605
|
+
if (!operationId) return;
|
|
606
|
+
const attemptId = String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
|
|
607
|
+
const previous = readClientUpdateState();
|
|
608
|
+
if (previous?.operationId && previous.operationId !== operationId) return;
|
|
609
|
+
if (previous?.attemptId && attemptId && previous.attemptId !== attemptId) return;
|
|
610
|
+
if (previous?.stage === 'connected'
|
|
611
|
+
&& previous?.restartVerified === true
|
|
612
|
+
&& stage !== 'connected') {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const targetVersion = String(process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION || '').trim().replace(/^v/i, '');
|
|
616
|
+
const productVersion = String(process.env.LIVEDESK_NPM_LAUNCHER_VERSION || readPackageVersion()).trim();
|
|
617
|
+
const state = {
|
|
618
|
+
...(previous && typeof previous === 'object' ? previous : {}),
|
|
619
|
+
operationId,
|
|
620
|
+
stage,
|
|
621
|
+
targetVersion,
|
|
622
|
+
productVersion,
|
|
623
|
+
agentVersion: readPackageVersion(),
|
|
624
|
+
launcherPid: process.pid,
|
|
625
|
+
attemptId,
|
|
626
|
+
updatedAt: new Date().toISOString(),
|
|
627
|
+
...details
|
|
628
|
+
};
|
|
629
|
+
mkdirSync(dirname(CLIENT_UPDATE_STATE_PATH), { recursive: true });
|
|
630
|
+
const temporaryPath = `${CLIENT_UPDATE_STATE_PATH}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
631
|
+
try {
|
|
632
|
+
writeFileSync(temporaryPath, JSON.stringify(state, null, 2), 'utf8');
|
|
633
|
+
try { chmodSync(temporaryPath, 0o600); } catch { /* Windows profile ACLs remain authoritative. */ }
|
|
634
|
+
renameSync(temporaryPath, CLIENT_UPDATE_STATE_PATH);
|
|
635
|
+
} catch {
|
|
636
|
+
rmSync(temporaryPath, { force: true });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
553
640
|
function hubCacheOwnerForSession(session) {
|
|
554
641
|
const userId = String(session?.user?.id || '').trim();
|
|
555
642
|
return userId ? `user:${userId}` : '';
|
|
@@ -3487,7 +3574,7 @@ function resolveBundledFfmpegPaths() {
|
|
|
3487
3574
|
return paths;
|
|
3488
3575
|
}
|
|
3489
3576
|
|
|
3490
|
-
function buildClientAgentEnvironment(prepared) {
|
|
3577
|
+
function buildClientAgentEnvironment(prepared) {
|
|
3491
3578
|
const env = { ...process.env };
|
|
3492
3579
|
env.LIVEDESK_CLIENT_PARENT_PID = String(process.pid);
|
|
3493
3580
|
// The client can be started by Explorer, a startup shortcut, or a GUI shell
|
|
@@ -3513,12 +3600,17 @@ function buildClientAgentEnvironment(prepared) {
|
|
|
3513
3600
|
env.LIVEDESK_NPX_CLI_PATH = npxCliPath;
|
|
3514
3601
|
}
|
|
3515
3602
|
env.LIVEDESK_CLIENT_MANAGER = String(prepared?.manager || env.LIVEDESK_CLIENT_MANAGER || '');
|
|
3516
|
-
env.LIVEDESK_CLIENT_PAIR_TOKEN = String(prepared?.pair || env.LIVEDESK_CLIENT_PAIR_TOKEN || '');
|
|
3517
|
-
env.LIVEDESK_CLIENT_NAME = String(prepared?.name || env.LIVEDESK_CLIENT_NAME || '');
|
|
3518
|
-
env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
|
|
3519
|
-
env.
|
|
3520
|
-
|
|
3521
|
-
|
|
3603
|
+
env.LIVEDESK_CLIENT_PAIR_TOKEN = String(prepared?.pair || env.LIVEDESK_CLIENT_PAIR_TOKEN || '');
|
|
3604
|
+
env.LIVEDESK_CLIENT_NAME = String(prepared?.name || env.LIVEDESK_CLIENT_NAME || '');
|
|
3605
|
+
env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
|
|
3606
|
+
env.LIVEDESK_DEVICE_ID = String(prepared?.deviceId || env.LIVEDESK_DEVICE_ID || '');
|
|
3607
|
+
env.LIVEDESK_CLIENT_ENGINE = String(prepared?.engine || env.LIVEDESK_CLIENT_ENGINE || 'auto');
|
|
3608
|
+
env.LIVEDESK_CLIENT_UPDATE_STATE_PATH = CLIENT_UPDATE_STATE_PATH;
|
|
3609
|
+
env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 = Buffer
|
|
3610
|
+
.from(JSON.stringify(buildClientUpdateRestartArgs(prepared)), 'utf8')
|
|
3611
|
+
.toString('base64');
|
|
3612
|
+
return env;
|
|
3613
|
+
}
|
|
3522
3614
|
|
|
3523
3615
|
function buildFastEnvironment(prepared) {
|
|
3524
3616
|
const env = buildClientAgentEnvironment(prepared);
|
|
@@ -3721,13 +3813,20 @@ function resolveFastLaunch(runtime) {
|
|
|
3721
3813
|
};
|
|
3722
3814
|
}
|
|
3723
3815
|
|
|
3724
|
-
function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3816
|
+
function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
3817
|
+
// Publish only a pre-spawn stage. A very fast child can reach Hub welcome
|
|
3818
|
+
// before spawn() returns; a post-spawn write would then erase its one-shot
|
|
3819
|
+
// connected proof.
|
|
3820
|
+
writeClientUpdateRuntimeStage('agent-launching', {
|
|
3821
|
+
agentPid: 0,
|
|
3822
|
+
restartVerified: false
|
|
3823
|
+
});
|
|
3824
|
+
const child = spawn(command, args, {
|
|
3825
|
+
env,
|
|
3826
|
+
stdio: 'inherit',
|
|
3827
|
+
windowsHide: true
|
|
3828
|
+
});
|
|
3829
|
+
activeAgentProcess = child;
|
|
3731
3830
|
if (typeof onStart === 'function') {
|
|
3732
3831
|
onStart(child);
|
|
3733
3832
|
}
|
|
@@ -3771,10 +3870,13 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
3771
3870
|
if (!isTruthy(process.env.LIVEDESK_UNIFIED_RUNTIME)) {
|
|
3772
3871
|
console.warn('[LiveDesk] @livedesk/client is deprecated. Use the unified livedesk package or LiveDesk Desktop.');
|
|
3773
3872
|
}
|
|
3774
|
-
const parsed = parseLauncherArgs(argv);
|
|
3775
|
-
if (parsed.updateWaitPid) {
|
|
3776
|
-
await waitForProcessExit(parsed.updateWaitPid);
|
|
3777
|
-
}
|
|
3873
|
+
const parsed = parseLauncherArgs(argv);
|
|
3874
|
+
if (parsed.updateWaitPid) {
|
|
3875
|
+
await waitForProcessExit(parsed.updateWaitPid);
|
|
3876
|
+
}
|
|
3877
|
+
writeClientUpdateRuntimeStage('launcher-started', {
|
|
3878
|
+
restartVerified: false
|
|
3879
|
+
});
|
|
3778
3880
|
if (parsed.help) {
|
|
3779
3881
|
printHelp();
|
|
3780
3882
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.171",
|
|
4
4
|
"description": "LiveDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"ws": "^8.18.3"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
43
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
44
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
45
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
42
|
+
"@livedesk/fast-linux-x64": "0.1.377",
|
|
43
|
+
"@livedesk/fast-osx-arm64": "0.1.377",
|
|
44
|
+
"@livedesk/fast-osx-x64": "0.1.377",
|
|
45
|
+
"@livedesk/fast-win-x64": "0.1.377"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|