@livedesk/client 0.1.215 → 0.1.217
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 +506 -555
- package/bin/livedesk-client-update-bootstrap.cjs +59 -0
- package/bin/livedesk-client.js +195 -58
- package/package.json +11 -11
- package/src/runtime/agent-process-lifecycle.js +85 -23
- package/src/runtime/client-runtime-server.js +719 -131
- package/src/runtime/hub-wake-listener.js +42 -11
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
|
|
3
3
|
const SIGNAL_EXIT_CODES = Object.freeze({ SIGINT: 130, SIGTERM: 143 });
|
|
4
|
-
const DEFAULT_WINDOWS_TREE_SNAPSHOT_MS = 10_000;
|
|
5
4
|
const WINDOWS_SPAWN_CLOCK_TOLERANCE_MS = 250;
|
|
6
5
|
|
|
7
6
|
function normalizeWindowsProcessRecord(value) {
|
|
@@ -109,6 +108,52 @@ export async function queryWindowsProcessTable({
|
|
|
109
108
|
.filter(Boolean);
|
|
110
109
|
}
|
|
111
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Captures only the named immutable identities. Normal Agent startup uses this
|
|
113
|
+
* for the Agent root plus its launcher owner instead of enumerating every
|
|
114
|
+
* process on the computer. Full-table discovery remains a terminal/recovery
|
|
115
|
+
* operation where descendants actually need to be found.
|
|
116
|
+
*/
|
|
117
|
+
export async function queryWindowsProcessIdentities(processIds, {
|
|
118
|
+
execFileImpl = execFile
|
|
119
|
+
} = {}) {
|
|
120
|
+
const ids = [...new Set((Array.isArray(processIds) ? processIds : [processIds])
|
|
121
|
+
.map(value => Number(value || 0))
|
|
122
|
+
.filter(value => Number.isInteger(value) && value > 1))]
|
|
123
|
+
.slice(0, 8);
|
|
124
|
+
if (ids.length === 0) return [];
|
|
125
|
+
const filter = ids.map(pid => `ProcessId = ${pid}`).join(' OR ');
|
|
126
|
+
const script = [
|
|
127
|
+
'$ErrorActionPreference = "Stop";',
|
|
128
|
+
`@(Get-CimInstance Win32_Process -Filter "${filter}" -Property ProcessId,ParentProcessId,CreationDate | ForEach-Object {`,
|
|
129
|
+
' $creation = [string]$_.CreationDate;',
|
|
130
|
+
' $ticks = if ($_.CreationDate) { [string]$_.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
131
|
+
' [pscustomobject]@{ ProcessId = [int]$_.ProcessId; ParentProcessId = [int]$_.ParentProcessId; CreationDate = $creation; CreationUtcTicks = $ticks }',
|
|
132
|
+
'}) | ConvertTo-Json -Compress'
|
|
133
|
+
].join(' ');
|
|
134
|
+
const result = await runExecFile(
|
|
135
|
+
'powershell.exe',
|
|
136
|
+
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script],
|
|
137
|
+
{ windowsHide: true, timeout: 5000, killSignal: 'SIGKILL', maxBuffer: 64 * 1024 },
|
|
138
|
+
execFileImpl
|
|
139
|
+
);
|
|
140
|
+
if (!result.ok || !result.stdout) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Windows process identity query failed: `
|
|
143
|
+
+ `${result.stderr || result.error?.message || 'CIM query returned no process records'}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = JSON.parse(result.stdout);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new Error(`Windows process identity query returned invalid JSON: ${error?.message || error}`);
|
|
151
|
+
}
|
|
152
|
+
return (Array.isArray(parsed) ? parsed : [parsed])
|
|
153
|
+
.map(normalizeWindowsProcessRecord)
|
|
154
|
+
.filter(Boolean);
|
|
155
|
+
}
|
|
156
|
+
|
|
112
157
|
/**
|
|
113
158
|
* Rechecks CreationDate and force-stops the single PID inside the same
|
|
114
159
|
* PowerShell process. PID reuse therefore becomes a harmless identity mismatch
|
|
@@ -157,17 +202,19 @@ export async function terminateExactWindowsProcessTree(recordOrRecords, {
|
|
|
157
202
|
|
|
158
203
|
/**
|
|
159
204
|
* Tracks the exact Agent root and only descendants connected through Windows
|
|
160
|
-
* ParentProcessId records.
|
|
161
|
-
*
|
|
162
|
-
*
|
|
205
|
+
* ParentProcessId records. Startup queries only the Agent and launcher
|
|
206
|
+
* identities. Full process-table snapshots are serialized and request-driven
|
|
207
|
+
* by terminal cleanup/recovery; a healthy connected Client owns no recurring
|
|
208
|
+
* PowerShell/CIM timer.
|
|
163
209
|
*/
|
|
164
210
|
export function createWindowsProcessTreeTracker(child, {
|
|
165
211
|
platform = process.platform,
|
|
166
212
|
queryProcessTableImpl = queryWindowsProcessTable,
|
|
167
|
-
|
|
213
|
+
queryInitialProcessRecordsImpl = null,
|
|
168
214
|
spawnedAtMs = Date.now(),
|
|
169
|
-
|
|
170
|
-
|
|
215
|
+
ownerPid = process.pid,
|
|
216
|
+
initialRetryDelaysMs = [25, 75, 150],
|
|
217
|
+
waitImpl = waitMilliseconds,
|
|
171
218
|
publishTrackedRecords = null,
|
|
172
219
|
reportPublisherError = error => console.warn(
|
|
173
220
|
`[LiveDesk Client] Windows owned-process manifest warning: ${error?.message || error}`
|
|
@@ -181,7 +228,6 @@ export function createWindowsProcessTreeTracker(child, {
|
|
|
181
228
|
let lastError = null;
|
|
182
229
|
let inFlight = null;
|
|
183
230
|
let stopped = false;
|
|
184
|
-
let timer = null;
|
|
185
231
|
let rootExitedAtMs = null;
|
|
186
232
|
let rootCapturedBeforeExit = false;
|
|
187
233
|
let lastSnapshotHadRootPid = false;
|
|
@@ -267,11 +313,22 @@ export function createWindowsProcessTreeTracker(child, {
|
|
|
267
313
|
}
|
|
268
314
|
};
|
|
269
315
|
|
|
270
|
-
const
|
|
316
|
+
const initialQuery = typeof queryInitialProcessRecordsImpl === 'function'
|
|
317
|
+
? queryInitialProcessRecordsImpl
|
|
318
|
+
: queryProcessTableImpl === queryWindowsProcessTable
|
|
319
|
+
? () => queryWindowsProcessIdentities([rootPid, ownerPid])
|
|
320
|
+
: queryProcessTableImpl;
|
|
321
|
+
const boundedInitialRetryDelays = (
|
|
322
|
+
Array.isArray(initialRetryDelaysMs) ? initialRetryDelaysMs : []
|
|
323
|
+
)
|
|
324
|
+
.slice(0, 5)
|
|
325
|
+
.map(value => Math.min(1000, Math.max(1, Number(value) || 1)));
|
|
326
|
+
|
|
327
|
+
const refresh = async ({ initial = false } = {}) => {
|
|
271
328
|
if (!enabled) return { ok: true, records: [] };
|
|
272
329
|
if (inFlight) return inFlight;
|
|
273
330
|
inFlight = Promise.resolve()
|
|
274
|
-
.then(() => queryProcessTableImpl())
|
|
331
|
+
.then(() => (initial ? initialQuery() : queryProcessTableImpl()))
|
|
275
332
|
.then(async snapshot => {
|
|
276
333
|
mergeSnapshot(snapshot);
|
|
277
334
|
lastError = null;
|
|
@@ -290,7 +347,7 @@ export function createWindowsProcessTreeTracker(child, {
|
|
|
290
347
|
} catch (error) {
|
|
291
348
|
// Manifest publication must never break the in-memory
|
|
292
349
|
// cleanup proof. Keep tracking, surface diagnostics,
|
|
293
|
-
// and retry on the next
|
|
350
|
+
// and retry on the next requested/terminal refresh.
|
|
294
351
|
lastPublisherError = error;
|
|
295
352
|
try { reportPublisherError(error); } catch { /* diagnostics cannot own lifecycle */ }
|
|
296
353
|
}
|
|
@@ -311,14 +368,20 @@ export function createWindowsProcessTreeTracker(child, {
|
|
|
311
368
|
return inFlight;
|
|
312
369
|
};
|
|
313
370
|
|
|
314
|
-
const ready = enabled
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
371
|
+
const ready = enabled
|
|
372
|
+
? (async () => {
|
|
373
|
+
let result = await refresh({ initial: true });
|
|
374
|
+
for (const retryDelayMs of boundedInitialRetryDelays) {
|
|
375
|
+
if (rootRecord || stopped) break;
|
|
376
|
+
// A just-spawned PID can briefly lag its CIM identity. Retry
|
|
377
|
+
// only the exact root/launcher filter; never compensate with
|
|
378
|
+
// a healthy-state full-table timer.
|
|
379
|
+
await waitImpl(retryDelayMs);
|
|
380
|
+
result = await refresh({ initial: true });
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
})()
|
|
384
|
+
: Promise.resolve({ ok: true, records: [] });
|
|
322
385
|
|
|
323
386
|
return {
|
|
324
387
|
enabled,
|
|
@@ -326,8 +389,6 @@ export function createWindowsProcessTreeTracker(child, {
|
|
|
326
389
|
refresh,
|
|
327
390
|
stop() {
|
|
328
391
|
stopped = true;
|
|
329
|
-
if (timer) clearIntervalImpl(timer);
|
|
330
|
-
timer = null;
|
|
331
392
|
},
|
|
332
393
|
getRootRecord: () => rootRecord ? { ...rootRecord } : null,
|
|
333
394
|
markRootExited(exitedAtMs = Date.now()) {
|
|
@@ -629,6 +690,7 @@ export function installAgentTerminationHandlers({
|
|
|
629
690
|
drainWindowsTree = drainOwnedWindowsAgentTreeUntilStopped,
|
|
630
691
|
windowsTerminateAttemptLimit = 3,
|
|
631
692
|
windowsTerminateRetryMs = 100,
|
|
693
|
+
windowsRecoveryRetryMs = 1000,
|
|
632
694
|
unixTerminateRetryMs = 1000,
|
|
633
695
|
reportTerminationFailure = message => console.error(message),
|
|
634
696
|
exitProcess = code => hostProcess.exit(code)
|
|
@@ -693,7 +755,7 @@ export function installAgentTerminationHandlers({
|
|
|
693
755
|
platform,
|
|
694
756
|
maxAttempts: windowsTerminateAttemptLimit,
|
|
695
757
|
retryMs: windowsTerminateRetryMs,
|
|
696
|
-
retryForeverMs:
|
|
758
|
+
retryForeverMs: windowsRecoveryRetryMs,
|
|
697
759
|
reportTerminationFailure
|
|
698
760
|
}));
|
|
699
761
|
}
|
|
@@ -712,7 +774,7 @@ export function installAgentTerminationHandlers({
|
|
|
712
774
|
child.livedeskWindowsDrainPromise = null;
|
|
713
775
|
windowsRetryTimer = setTimeout(
|
|
714
776
|
drainUntilStopped,
|
|
715
|
-
Math.max(100, Number(
|
|
777
|
+
Math.max(100, Number(windowsRecoveryRetryMs) || 1000)
|
|
716
778
|
);
|
|
717
779
|
});
|
|
718
780
|
};
|