@livedesk/client 0.1.202 → 0.1.203

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/README.md CHANGED
@@ -62,9 +62,9 @@ System Settings path when access is missing; after granting access, restart the
62
62
  client. When file transfer is enabled by the Hub, received files are saved
63
63
  to `Desktop/LiveDeskFiles` unless the Hub sets another destination folder.
64
64
 
65
- By default, the launcher uses the packaged C# RemoteFast engine when supported.
66
- It falls back to the Node engine for AI assist or when a compatible RemoteFast
67
- runtime is unavailable. Windows, macOS, and Linux all try RemoteFast first so
65
+ By default, the launcher uses the packaged C# RemoteFast engine when supported.
66
+ It falls back to the Node engine only when a compatible RemoteFast runtime is
67
+ unavailable. Windows, macOS, and Linux all try RemoteFast first so
68
68
  the Hub can request the Mode 3 hardware video path when it is available.
69
69
  RemoteFast is shipped as a platform-specific self-contained .NET executable,
70
70
  so client computers do not need a separate .NET installation.
@@ -1571,15 +1571,16 @@ function buildClientUpdateBootstrapScript() {
1571
1571
  'const isGroupAlive = pid => { try { process.kill(-pid, 0); return true; } catch (error) { return error?.code === "EPERM"; } };',
1572
1572
  'const runWindowsPowerShell = script => new Promise((resolve, reject) => { execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { windowsHide: true, timeout: 15000, killSignal: "SIGKILL", maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => { if (error) { reject(new Error(String(stderr || error.message || "PowerShell failed").trim())); return; } resolve(String(stdout || "").trim()); }); });',
1573
1573
  'const queryWindowsProcessTable = async () => { const stdout = await runWindowsPowerShell(`$ErrorActionPreference = "Stop"; $records = @(Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate | ForEach-Object { $ticks = if ($_.CreationDate) { [string]$_.CreationDate.ToUniversalTime().Ticks } else { "" }; [pscustomobject]@{ ProcessId = [int]$_.ProcessId; ParentProcessId = [int]$_.ParentProcessId; CreationUtcTicks = $ticks } }); [pscustomobject]@{ Records = $records } | ConvertTo-Json -Compress -Depth 4`); if (!stdout) throw new Error("Windows process snapshot returned no data"); const parsed = JSON.parse(stdout); const values = parsed?.Records == null ? [] : (Array.isArray(parsed.Records) ? parsed.Records : [parsed.Records]); return values.map(item => ({ pid: Number(item?.ProcessId || 0), parentPid: Number(item?.ParentProcessId || 0), startOrder: String(item?.CreationUtcTicks || "") })).filter(item => Number.isInteger(item.pid) && item.pid > 1 && /^\\d+$/.test(item.startOrder)); };',
1574
+ 'const queryTrackedWindowsProcessTable = child => { if (child.livedeskWindowsSnapshotPromise) return child.livedeskWindowsSnapshotPromise; const pending = queryWindowsProcessTable(); child.livedeskWindowsSnapshotPromise = pending; const clear = () => { if (child.livedeskWindowsSnapshotPromise === pending) child.livedeskWindowsSnapshotPromise = null; }; pending.then(clear, clear); return pending; };',
1574
1575
  'const sameWindowsIdentity = (left, right) => Number(left?.pid || 0) === Number(right?.pid || 0) && String(left?.startOrder || "") === String(right?.startOrder || "");',
1575
- 'const captureWindowsIdentity = async pid => { const processId = Number(pid || 0); const deadline = Date.now() + 2500; let lastError = null; do { try { const snapshot = await queryWindowsProcessTable(); const identity = snapshot.find(item => item.pid === processId) || null; if (identity) return identity; } catch (error) { lastError = error; } if (Date.now() < deadline) await sleep(50); } while (Date.now() < deadline); throw new Error(`Could not capture immutable Windows CreationDate for pid=${processId}: ${lastError?.message || "process not visible"}`); };',
1576
+ 'const captureWindowsIdentity = async child => { const processId = Number(child?.pid || 0); const deadline = Date.now() + 2500; let lastError = null; do { try { const snapshot = await queryTrackedWindowsProcessTable(child); const identity = snapshot.find(item => item.pid === processId) || null; if (identity) return identity; } catch (error) { lastError = error; } if (Date.now() < deadline) await sleep(50); } while (Date.now() < deadline); throw new Error(`Could not capture immutable Windows CreationDate for pid=${processId}: ${lastError?.message || "process not visible"}`); };',
1576
1577
  'const windowsTicksAtMilliseconds = value => BigInt(Math.trunc(Number(value) || Date.now())) * 10000n + 621355968000000000n;',
1577
1578
  'const setWindowsRootLifetimeEnd = (child, ticks) => { const candidate = BigInt(ticks); const previous = child.livedeskWindowsRootLifetimeEndTicks; if (!previous || candidate < BigInt(previous)) child.livedeskWindowsRootLifetimeEndTicks = String(candidate); return BigInt(child.livedeskWindowsRootLifetimeEndTicks); };',
1578
1579
  'const mergeRootAbsentWindowsTree = (child, rootPid, snapshot) => { const tracked = child.livedeskWindowsTrackedRecords || (child.livedeskWindowsTrackedRecords = new Map()); const currentByPid = new Map(snapshot.map(item => [item.pid, item])); const exactCurrentByPid = new Map(); for (const record of tracked.values()) { const current = currentByPid.get(record.pid); if (sameWindowsIdentity(current, record)) exactCurrentByPid.set(record.pid, record); } const lowerBound = windowsTicksAtMilliseconds(Number(child.livedeskWindowsSpawnedAtMs || Date.now()) - 2000); const upperBound = child.livedeskWindowsRootLifetimeEndTicks ? BigInt(child.livedeskWindowsRootLifetimeEndTicks) : null; let changed = true; while (changed) { changed = false; for (const record of snapshot) { const key = `${record.pid}:${record.startOrder}`; if (record.pid === rootPid || tracked.has(key)) continue; let recordStart; try { recordStart = BigInt(record.startOrder); if (recordStart < lowerBound || (upperBound && recordStart > upperBound)) continue; } catch { continue; } const parent = record.parentPid === rootPid ? { depth: 0 } : exactCurrentByPid.get(record.parentPid); if (!parent) continue; const descendant = { ...record, depth: Number(parent.depth || 0) + 1 }; tracked.set(key, descendant); exactCurrentByPid.set(descendant.pid, descendant); changed = true; } } return tracked; };',
1579
1580
  'const mergeExactWindowsTree = (child, root, snapshot) => { const tracked = child.livedeskWindowsTrackedRecords || (child.livedeskWindowsTrackedRecords = new Map()); const currentByPid = new Map(snapshot.map(item => [item.pid, item])); const currentRoot = currentByPid.get(root.pid) || null; const rootIsExact = sameWindowsIdentity(currentRoot, root); const rootWasReused = Boolean(currentRoot && !rootIsExact); if (rootWasReused) setWindowsRootLifetimeEnd(child, BigInt(currentRoot.startOrder) - 1n); else if (!currentRoot && child.livedeskWindowsRootExitedAtMs) setWindowsRootLifetimeEnd(child, windowsTicksAtMilliseconds(child.livedeskWindowsRootExitedAtMs)); const rootLifetimeClosed = Boolean(child.livedeskWindowsRootLifetimeEndTicks); if (rootIsExact) tracked.set(`${root.pid}:${root.startOrder}`, { ...root, depth: 0 }); const exactCurrentByPid = new Map(); if (rootIsExact) exactCurrentByPid.set(root.pid, { ...root, depth: 0 }); for (const record of tracked.values()) { const current = currentByPid.get(record.pid); if (sameWindowsIdentity(current, record)) exactCurrentByPid.set(record.pid, record); } let changed = true; while (changed) { changed = false; for (const record of snapshot) { const key = `${record.pid}:${record.startOrder}`; if (record.pid === root.pid || tracked.has(key)) continue; let recordStart; try { recordStart = BigInt(record.startOrder); if (recordStart < BigInt(root.startOrder)) continue; if (rootLifetimeClosed && recordStart > BigInt(child.livedeskWindowsRootLifetimeEndTicks)) continue; } catch { continue; } let parent = exactCurrentByPid.get(record.parentPid) || null; if (!parent && !currentRoot && rootLifetimeClosed && record.parentPid === root.pid) parent = { ...root, depth: 0 }; if (!parent) continue; const descendant = { ...record, depth: Number(parent.depth || 0) + 1 }; tracked.set(key, descendant); exactCurrentByPid.set(descendant.pid, descendant); changed = true; } } return tracked; };',
1580
- 'const refreshTrackedWindowsTree = async (child, root = child.livedeskWindowsIdentity || null) => { const snapshot = await queryWindowsProcessTable(); if (root) mergeExactWindowsTree(child, root, snapshot); else mergeRootAbsentWindowsTree(child, Number(child.pid || 0), snapshot); return snapshot; };',
1581
+ 'const refreshTrackedWindowsTree = async (child, root = child.livedeskWindowsIdentity || null) => { const snapshot = await queryTrackedWindowsProcessTable(child); if (root) mergeExactWindowsTree(child, root, snapshot); else mergeRootAbsentWindowsTree(child, Number(child.pid || 0), snapshot); return snapshot; };',
1581
1582
  'const stopExactWindowsRecords = async records => { if (!Array.isArray(records) || records.length === 0) return; const encoded = Buffer.from(JSON.stringify(records.map(record => ({ pid: record.pid, startOrder: record.startOrder }))), "utf8").toString("base64"); const script = `$ErrorActionPreference = "Stop"; $targetsJson = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("${encoded}")); $targets = ConvertFrom-Json -InputObject $targetsJson; $failures = [System.Collections.Generic.List[string]]::new(); foreach ($item in $targets) { $targetPid = [int]$item.pid; $expectedStartTicks = [string]$item.startOrder; try { $target = Get-CimInstance Win32_Process -Filter "ProcessId = $targetPid" -Property ProcessId,CreationDate -ErrorAction SilentlyContinue | Select-Object -First 1; if (-not $target) { continue }; $targetStartTicks = if ($target.CreationDate) { [string]$target.CreationDate.ToUniversalTime().Ticks } else { "" }; if ($targetStartTicks -ne $expectedStartTicks) { continue }; Stop-Process -Id $targetPid -Force -ErrorAction Stop } catch { $failures.Add("pid=$targetPid $($_.Exception.Message)") } }; if ($failures.Count -gt 0) { [Console]::Error.WriteLine(($failures -join "; ")); exit 1 }`; await runWindowsPowerShell(script); };',
1582
- 'const trackWindowsChild = child => { if (process.platform !== "win32" || Number(child?.pid || 0) <= 1 || child.livedeskWindowsIdentityPromise) return; child.livedeskWindowsTrackedRecords = new Map(); child.livedeskWindowsSpawnedAtMs = Date.now(); child.livedeskWindowsRootExitedAtMs = null; child.livedeskWindowsTrackingStopped = false; child.once("exit", () => { if (!child.livedeskWindowsRootExitedAtMs) child.livedeskWindowsRootExitedAtMs = Date.now(); setWindowsRootLifetimeEnd(child, windowsTicksAtMilliseconds(child.livedeskWindowsRootExitedAtMs)); }); const refresh = () => { void refreshTrackedWindowsTree(child).then(() => { child.livedeskWindowsTrackingError = null; }).catch(error => { child.livedeskWindowsTrackingError = error; }); }; refresh(); child.livedeskWindowsTrackingTimer = setInterval(refresh, 250); child.livedeskWindowsTrackingTimer.unref?.(); child.livedeskWindowsIdentityPromise = (async () => { while (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) { try { const identity = await captureWindowsIdentity(child.pid); child.livedeskWindowsIdentity = identity; refresh(); return identity; } catch (error) { child.livedeskWindowsIdentityError = error; if (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) await sleep(250); } } return null; })(); };',
1583
+ 'const trackWindowsChild = child => { if (process.platform !== "win32" || Number(child?.pid || 0) <= 1 || child.livedeskWindowsIdentityPromise) return; child.livedeskWindowsTrackedRecords = new Map(); child.livedeskWindowsSpawnedAtMs = Date.now(); child.livedeskWindowsRootExitedAtMs = null; child.livedeskWindowsTrackingStopped = false; child.once("exit", () => { if (!child.livedeskWindowsRootExitedAtMs) child.livedeskWindowsRootExitedAtMs = Date.now(); setWindowsRootLifetimeEnd(child, windowsTicksAtMilliseconds(child.livedeskWindowsRootExitedAtMs)); }); const refresh = () => { void refreshTrackedWindowsTree(child).then(() => { child.livedeskWindowsTrackingError = null; }).catch(error => { child.livedeskWindowsTrackingError = error; }); }; refresh(); child.livedeskWindowsTrackingTimer = setInterval(refresh, 250); child.livedeskWindowsTrackingTimer.unref?.(); child.livedeskWindowsIdentityPromise = (async () => { while (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) { try { const identity = await captureWindowsIdentity(child); child.livedeskWindowsIdentity = identity; refresh(); return identity; } catch (error) { child.livedeskWindowsIdentityError = error; if (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) await sleep(250); } } return null; })(); };',
1583
1584
  'const stopWindowsTracking = child => { child.livedeskWindowsTrackingStopped = true; if (child?.livedeskWindowsTrackingTimer) clearInterval(child.livedeskWindowsTrackingTimer); child.livedeskWindowsTrackingTimer = null; };',
1584
1585
  'const terminateExactWindowsTrackedTree = (child, root) => { if (child.livedeskWindowsDrainPromise) return child.livedeskWindowsDrainPromise; child.livedeskWindowsDrainPromise = (async () => { let attempt = 0; let emptyPasses = 0; for (;;) { attempt += 1; try { const snapshot = await refreshTrackedWindowsTree(child, root); const currentByPid = new Map(snapshot.map(item => [item.pid, item])); const remaining = [...child.livedeskWindowsTrackedRecords.values()].filter(target => sameWindowsIdentity(currentByPid.get(target.pid), target)).sort((left, right) => Number(right.depth || 0) - Number(left.depth || 0)); if (remaining.length === 0) { emptyPasses += 1; if (emptyPasses >= 2) { stopWindowsTracking(child); return; } } else { emptyPasses = 0; await stopExactWindowsRecords(remaining); } child.livedeskWindowsTrackingError = null; } catch (error) { emptyPasses = 0; child.livedeskWindowsTrackingError = error; if (attempt === 1 || attempt % 10 === 0) process.stderr.write(`LiveDesk update cleanup is retrying exact Windows process-tree drain: ${error?.message || error}\\n`); } await sleep(Math.min(1000, 100 + (attempt * 50))); } })(); return child.livedeskWindowsDrainPromise; };',
1585
1586
  'const stateLockPath = statePath + ".lock"; const lockWaitBuffer = new Int32Array(new SharedArrayBuffer(4));',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.202",
3
+ "version": "0.1.203",
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.410",
45
- "@livedesk/fast-osx-arm64": "0.1.410",
46
- "@livedesk/fast-osx-x64": "0.1.410",
47
- "@livedesk/fast-win-x64": "0.1.410"
44
+ "@livedesk/fast-linux-x64": "0.1.411",
45
+ "@livedesk/fast-osx-arm64": "0.1.411",
46
+ "@livedesk/fast-osx-x64": "0.1.411",
47
+ "@livedesk/fast-win-x64": "0.1.411"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"