@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.
@@ -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. The default 10-second cadence is deliberately low
161
- * overhead; refreshes are serialized and the timer is unref'd. A final
162
- * snapshot covers normal direct FFmpeg children even between timer samples.
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
- snapshotIntervalMs = DEFAULT_WINDOWS_TREE_SNAPSHOT_MS,
213
+ queryInitialProcessRecordsImpl = null,
168
214
  spawnedAtMs = Date.now(),
169
- setIntervalImpl = setInterval,
170
- clearIntervalImpl = clearInterval,
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 refresh = async () => {
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 cadence/terminal refresh.
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 ? refresh() : Promise.resolve({ ok: true, records: [] });
315
- if (enabled) {
316
- const boundedInterval = Math.max(5000, Number(snapshotIntervalMs) || DEFAULT_WINDOWS_TREE_SNAPSHOT_MS);
317
- timer = setIntervalImpl(() => {
318
- if (!stopped) void refresh();
319
- }, boundedInterval);
320
- timer?.unref?.();
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: windowsTerminateRetryMs,
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(windowsTerminateRetryMs) || 100)
777
+ Math.max(100, Number(windowsRecoveryRetryMs) || 1000)
716
778
  );
717
779
  });
718
780
  };