livedesk 0.1.389 → 0.1.390

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.
@@ -1061,14 +1061,14 @@ function readReplacementOutput(outputPath) {
1061
1061
  }
1062
1062
  }
1063
1063
 
1064
- function listWindowsProcessTree() {
1064
+ export function listWindowsProcessTree() {
1065
1065
  const script = [
1066
1066
  '$ErrorActionPreference = "Stop"',
1067
1067
  '[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)',
1068
- 'Get-CimInstance Win32_Process',
1069
- '| Select-Object ProcessId,ParentProcessId,Name,ExecutablePath,CommandLine',
1070
- '| ConvertTo-Json -Compress'
1071
- ].join(' ');
1068
+ 'Get-CimInstance Win32_Process'
1069
+ + ' | Select-Object ProcessId,ParentProcessId,Name,ExecutablePath,CommandLine'
1070
+ + ' | ConvertTo-Json -Compress'
1071
+ ].join('; ');
1072
1072
  const encoded = Buffer.from(script, 'utf16le').toString('base64');
1073
1073
  const result = spawnSync(
1074
1074
  'powershell.exe',
@@ -2888,15 +2888,18 @@ async function startConnectionChoiceServer(supabase, options = {}) {
2888
2888
 
2889
2889
  async function chooseClientConnection(supabase, options = {}) {
2890
2890
  if (isTruthy(process.env.LIVEDESK_UNIFIED_RUNTIME)) {
2891
- const connectionPage = createClientRuntimeServer({
2892
- host: process.env.LIVEDESK_CLIENT_RUNTIME_HOST || '127.0.0.1',
2893
- port: options.authPort || DEFAULT_AUTH_CALLBACK_PORT,
2894
- webDist: process.env.LIVEDESK_WEB_DIST || '',
2891
+ const connectionPage = createClientRuntimeServer({
2892
+ host: process.env.LIVEDESK_CLIENT_RUNTIME_HOST || '127.0.0.1',
2893
+ port: options.authPort || DEFAULT_AUTH_CALLBACK_PORT,
2894
+ webDist: process.env.LIVEDESK_WEB_DIST || '',
2895
2895
  appVersion: process.env.LIVEDESK_NPM_LAUNCHER_VERSION || readPackageVersion(),
2896
- deviceId: options.deviceId,
2897
- engine: options.engine,
2896
+ deviceId: options.deviceId,
2897
+ engine: options.engine,
2898
2898
  savedSession: options.savedSession,
2899
+ initialChoice: options.initialChoice,
2900
+ initialChoiceMessage: options.initialChoiceMessage,
2899
2901
  beginGoogleSignIn: async redirectTo => {
2902
+ if (!supabase?.auth) throw new Error('supabase-session-required');
2900
2903
  const { data, error } = await supabase.auth.signInWithOAuth({
2901
2904
  provider: 'google',
2902
2905
  options: {
@@ -2910,15 +2913,22 @@ async function chooseClientConnection(supabase, options = {}) {
2910
2913
  return { url: data.url };
2911
2914
  },
2912
2915
  exchangeGoogleCode: async code => {
2916
+ if (!supabase?.auth) throw new Error('supabase-session-required');
2913
2917
  const { data, error } = await supabase.auth.exchangeCodeForSession(code);
2914
2918
  if (error) throw error;
2915
2919
  if (!data?.session) throw new Error('google-session-missing');
2916
2920
  if (!writeSavedSessionToFile(data.session)) throw new Error('refresh-token-required');
2917
2921
  return data.session;
2918
2922
  },
2919
- resolvePin: pin => resolveManagerFromPin(supabase, pin),
2920
- changeRole: async (role, snapshot) => {
2921
- const session = await refreshSessionIfNeeded(supabase);
2923
+ resolvePin: pin => {
2924
+ if (!supabase) throw new Error('supabase-session-required');
2925
+ return resolveManagerFromPin(supabase, pin);
2926
+ },
2927
+ changeRole: async (role, snapshot) => {
2928
+ if (!supabase) {
2929
+ return { ok: false, error: 'supabase-session-required' };
2930
+ }
2931
+ const session = await refreshSessionIfNeeded(supabase);
2922
2932
  if (!session?.access_token) {
2923
2933
  return { ok: false, error: 'supabase-session-required' };
2924
2934
  }
@@ -3255,7 +3265,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
3255
3265
  let connectionPage = null;
3256
3266
  let discoverySource = '';
3257
3267
 
3258
- if (shouldLogin) {
3268
+ if (shouldLogin) {
3259
3269
  const supabase = await createSupabaseClient();
3260
3270
  const startupArgs = buildStartupClientArgs(parsed);
3261
3271
  let savedSession = null;
@@ -3362,10 +3372,38 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
3362
3372
  message: `Connected to LiveDesk Hub at ${manager}.`
3363
3373
  });
3364
3374
  }
3365
- console.log(`Found LiveDesk Hub at ${manager}.`);
3366
- }
3367
-
3368
- const slot = normalizeSlotNumber(parsed.slot);
3375
+ console.log(`Found LiveDesk Hub at ${manager}.`);
3376
+ }
3377
+ // Exact-package updates preserve the already paired Hub endpoint and pass
3378
+ // --no-login so the replacement cannot block on browser auth. The unified
3379
+ // launcher must still own its local runtime/status API; the package
3380
+ // supervisor uses that API to prove the new launcher and Agent are stable
3381
+ // before it commits the update.
3382
+ if (!shouldLogin
3383
+ && !existingConnectionPage
3384
+ && isTruthy(process.env.LIVEDESK_UNIFIED_RUNTIME)
3385
+ && manager
3386
+ && pair) {
3387
+ const preservedSession = readSavedSessionFromFile();
3388
+ const explicitConnection = await chooseClientConnection(null, {
3389
+ authPort: parsed.authPort,
3390
+ deviceId: parsed.deviceId,
3391
+ engine: parsed.engine,
3392
+ savedSession: preservedSession,
3393
+ openBrowser: false,
3394
+ initialChoice: {
3395
+ type: 'explicit',
3396
+ manager,
3397
+ pair,
3398
+ endpointCandidates: [manager],
3399
+ ...(preservedSession?.access_token ? { session: preservedSession } : {})
3400
+ },
3401
+ initialChoiceMessage: `Using the existing LiveDesk Hub pairing at ${manager}.`
3402
+ });
3403
+ connectionPage = explicitConnection.connectionPage || null;
3404
+ }
3405
+
3406
+ const slot = normalizeSlotNumber(parsed.slot);
3369
3407
  if (manager) {
3370
3408
  forwarded = upsertForwardedOption(forwarded, '--manager', manager);
3371
3409
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.172",
3
+ "version": "0.1.173",
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.378",
43
- "@livedesk/fast-osx-arm64": "0.1.378",
44
- "@livedesk/fast-osx-x64": "0.1.378",
45
- "@livedesk/fast-win-x64": "0.1.378"
42
+ "@livedesk/fast-linux-x64": "0.1.379",
43
+ "@livedesk/fast-osx-arm64": "0.1.379",
44
+ "@livedesk/fast-osx-x64": "0.1.379",
45
+ "@livedesk/fast-win-x64": "0.1.379"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"
@@ -514,9 +514,16 @@ function sendText(res, status, body, type = 'text/plain; charset=utf-8') {
514
514
  res.end(body);
515
515
  }
516
516
 
517
- export function createClientRuntimeServer(options = {}) {
518
- const host = normalizeString(options.host, 80) || DEFAULT_HOST;
517
+ export function createClientRuntimeServer(options = {}) {
518
+ const host = normalizeString(options.host, 80) || DEFAULT_HOST;
519
519
  const port = normalizePort(options.port, DEFAULT_PORT);
520
+ const initialChoice = options.initialChoice && typeof options.initialChoice === 'object'
521
+ ? options.initialChoice
522
+ : null;
523
+ const initialChoiceMessage = normalizeString(
524
+ options.initialChoiceMessage,
525
+ 1000
526
+ ) || 'Existing LiveDesk credentials accepted. Starting the Client.';
520
527
  const trustedWebOrigins = createTrustedWebOrigins(port);
521
528
  const webDist = resolve(String(options.webDist || process.env.LIVEDESK_WEB_DIST || '').trim() || join(process.cwd(), 'apps', 'web', 'dist'));
522
529
  const deviceId = normalizeString(options.deviceId || process.env.LIVEDESK_DEVICE_ID, 160);
@@ -1030,23 +1037,29 @@ export function createClientRuntimeServer(options = {}) {
1030
1037
  else res.end();
1031
1038
  });
1032
1039
  });
1033
- server.once('error', rejectStart);
1034
- server.listen(port, host, () => {
1035
- runtime.emit('runtime.http.started', { host, port });
1036
- const saved = normalizeRuntimeAuthSession(options.savedSession, { requireRefreshToken: true });
1037
- if (saved.ok) {
1040
+ server.once('error', rejectStart);
1041
+ server.listen(port, host, () => {
1042
+ runtime.emit('runtime.http.started', { host, port });
1043
+ if (initialChoice) {
1038
1044
  setImmediate(() => {
1039
- try {
1040
- if (!writeSavedSession(saved.session)) throw new Error('refresh-token-required');
1041
- state.auth.persisted = true;
1042
- complete({ type: 'google', session: saved.session }, 'Saved sign-in found. Finding the LiveDesk Hub.');
1043
- } catch (error) {
1044
- recordAuthAttempt('rejected', `session-persistence-failed:${normalizeString(error?.message || error, 160)}`);
1045
- }
1045
+ complete(initialChoice, initialChoiceMessage);
1046
1046
  });
1047
+ } else {
1048
+ const saved = normalizeRuntimeAuthSession(options.savedSession, { requireRefreshToken: true });
1049
+ if (saved.ok) {
1050
+ setImmediate(() => {
1051
+ try {
1052
+ if (!writeSavedSession(saved.session)) throw new Error('refresh-token-required');
1053
+ state.auth.persisted = true;
1054
+ complete({ type: 'google', session: saved.session }, 'Saved sign-in found. Finding the LiveDesk Hub.');
1055
+ } catch (error) {
1056
+ recordAuthAttempt('rejected', `session-persistence-failed:${normalizeString(error?.message || error, 160)}`);
1057
+ }
1058
+ });
1059
+ }
1047
1060
  }
1048
- resolveStart();
1049
- });
1061
+ resolveStart();
1062
+ });
1050
1063
  });
1051
1064
 
1052
1065
  return {
@@ -10,6 +10,7 @@ export const LIVE_DESK_DEDICATED_CLIENT_UPDATE_MIN_VERSION = '0.1.172';
10
10
  export const LIVE_DESK_UPDATE_CLIENT_BATCH_SIZE = 5;
11
11
  export const LIVE_DESK_UPDATE_TARGET_TIMEOUT_MS = 480_000;
12
12
  export const LIVE_DESK_UPDATE_TIMEOUT_MS = 90 * 60_000;
13
+ export const LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS = 60_000;
13
14
  export const LIVE_DESK_UPDATE_CHECK_INTERVAL_MS = 60_000;
14
15
  export const LIVE_DESK_LEGACY_COMMAND_MAX_LENGTH = 3900;
15
16
 
@@ -195,6 +196,7 @@ function statusForRun(run) {
195
196
  pendingOfflineCount,
196
197
  failedCount,
197
198
  batchSize: run.batchSize,
199
+ clientRestartStabilityMs: run.clientRestartStabilityMs,
198
200
  latestManagerVersion: run.latestManagerVersion,
199
201
  latestClientVersion: run.latestClientVersion,
200
202
  error: run.error || '',
@@ -218,8 +220,18 @@ function deviceNeedsUpdate(device, latestManagerVersion, latestClientVersion) {
218
220
  return !isVersionAtLeast(device?.productVersion, latestManagerVersion)
219
221
  || !isVersionAtLeast(device?.agentVersion, latestClientVersion);
220
222
  }
221
-
222
- export function createLiveDeskUpdateManager({
223
+
224
+ function resetReconnectCandidate(target) {
225
+ target.candidateSessionId = '';
226
+ target.candidatePid = 0;
227
+ target.candidateConnectedAt = '';
228
+ target.candidateProductVersion = '';
229
+ target.candidateAgentVersion = '';
230
+ target.candidateSince = '';
231
+ target.stabilityDeadlineAt = '';
232
+ }
233
+
234
+ export function createLiveDeskUpdateManager({
223
235
  remoteHub,
224
236
  currentManagerVersion,
225
237
  currentClientVersion,
@@ -229,7 +241,8 @@ export function createLiveDeskUpdateManager({
229
241
  now = () => Date.now(),
230
242
  clientBatchSize = LIVE_DESK_UPDATE_CLIENT_BATCH_SIZE,
231
243
  targetTimeoutMs = LIVE_DESK_UPDATE_TARGET_TIMEOUT_MS,
232
- operationTimeoutMs = LIVE_DESK_UPDATE_TIMEOUT_MS
244
+ operationTimeoutMs = LIVE_DESK_UPDATE_TIMEOUT_MS,
245
+ clientRestartStabilityMs = LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS
233
246
  }) {
234
247
  let latestRelease = null;
235
248
  let checkError = '';
@@ -249,6 +262,12 @@ export function createLiveDeskUpdateManager({
249
262
  effectiveTargetTimeoutMs,
250
263
  Number(operationTimeoutMs) || LIVE_DESK_UPDATE_TIMEOUT_MS
251
264
  );
265
+ const effectiveClientRestartStabilityMs = Math.max(
266
+ 0,
267
+ Number.isFinite(Number(clientRestartStabilityMs))
268
+ ? Number(clientRestartStabilityMs)
269
+ : LIVE_DESK_UPDATE_CLIENT_RESTART_STABILITY_MS
270
+ );
252
271
 
253
272
  const touch = () => {
254
273
  if (run) run.updatedAt = new Date(now()).toISOString();
@@ -323,6 +342,7 @@ export function createLiveDeskUpdateManager({
323
342
  pendingOfflineCount: 0,
324
343
  failedCount: 0,
325
344
  batchSize: effectiveClientBatchSize,
345
+ clientRestartStabilityMs: effectiveClientRestartStabilityMs,
326
346
  targets: []
327
347
  })
328
348
  };
@@ -405,6 +425,15 @@ export function createLiveDeskUpdateManager({
405
425
  const dispatchTarget = (target, device) => {
406
426
  const dispatchedAtEpochMs = now();
407
427
  const updateDeadlineEpochMs = dispatchedAtEpochMs + effectiveTargetTimeoutMs;
428
+ const dispatchedSessionId = String(device?.sessionId || '').trim();
429
+ if (!dispatchedSessionId) {
430
+ target.state = 'failed';
431
+ target.error = 'client-update-session-unavailable';
432
+ return false;
433
+ }
434
+ target.dispatchedSessionId = dispatchedSessionId;
435
+ target.dispatchedPid = Math.max(0, Math.floor(Number(device?.pid) || 0));
436
+ resetReconnectCandidate(target);
408
437
  const supportsDedicated = device.capabilities?.clientUpdate === true
409
438
  && isVersionAtLeast(device.agentVersion, LIVE_DESK_DEDICATED_CLIENT_UPDATE_MIN_VERSION);
410
439
  const payload = {
@@ -434,6 +463,9 @@ export function createLiveDeskUpdateManager({
434
463
  target.commandId = '';
435
464
  target.dispatchedAt = '';
436
465
  target.deadlineAt = '';
466
+ target.dispatchedSessionId = '';
467
+ target.dispatchedPid = 0;
468
+ resetReconnectCandidate(target);
437
469
  target.error = '';
438
470
  return 'pending-offline';
439
471
  }
@@ -497,14 +529,50 @@ export function createLiveDeskUpdateManager({
497
529
  for (const target of run.targets) {
498
530
  if (target.state === 'failed' || target.state === 'completed') continue;
499
531
  const device = devices.get(target.deviceId);
500
- if (target.state === 'waiting'
501
- && device?.connected === true
502
- && !deviceNeedsUpdate(device, run.latestManagerVersion, run.latestClientVersion)) {
503
- target.state = 'completed';
504
- target.currentVersion = String(device.agentVersion || '');
505
- target.currentProductVersion = String(device.productVersion || '');
506
- target.completedAt = new Date(currentTime).toISOString();
507
- continue;
532
+ if (target.state === 'waiting') {
533
+ const sessionId = String(device?.sessionId || '').trim();
534
+ const connectedAt = String(device?.connectedAt || '');
535
+ const connectedAtEpochMs = Date.parse(connectedAt);
536
+ const dispatchedAtEpochMs = Date.parse(target.dispatchedAt || '');
537
+ 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 qualifiesForStability = device?.connected === true
541
+ && !!sessionId
542
+ && candidatePid > 1
543
+ && sessionId !== target.dispatchedSessionId
544
+ && connectedAtEpochMs >= dispatchedAtEpochMs
545
+ && !deviceNeedsUpdate(device, run.latestManagerVersion, run.latestClientVersion);
546
+ const sameCandidate = qualifiesForStability
547
+ && target.candidateSessionId === sessionId
548
+ && target.candidatePid === candidatePid
549
+ && target.candidateConnectedAt === connectedAt
550
+ && target.candidateProductVersion === candidateProductVersion
551
+ && target.candidateAgentVersion === candidateAgentVersion;
552
+
553
+ if (!qualifiesForStability) {
554
+ resetReconnectCandidate(target);
555
+ } else if (!sameCandidate) {
556
+ target.candidateSessionId = sessionId;
557
+ target.candidatePid = candidatePid;
558
+ target.candidateConnectedAt = connectedAt;
559
+ target.candidateProductVersion = candidateProductVersion;
560
+ target.candidateAgentVersion = candidateAgentVersion;
561
+ target.candidateSince = new Date(currentTime).toISOString();
562
+ target.stabilityDeadlineAt = new Date(
563
+ currentTime + run.clientRestartStabilityMs
564
+ ).toISOString();
565
+ }
566
+
567
+ if (qualifiesForStability
568
+ && target.candidateSessionId === sessionId
569
+ && currentTime >= Date.parse(target.stabilityDeadlineAt || '')) {
570
+ target.state = 'completed';
571
+ target.currentVersion = String(device.agentVersion || '');
572
+ target.currentProductVersion = String(device.productVersion || '');
573
+ target.completedAt = new Date(currentTime).toISOString();
574
+ continue;
575
+ }
508
576
  }
509
577
  if (target.state === 'waiting'
510
578
  && Date.parse(target.deadlineAt || '') > 0
@@ -579,6 +647,7 @@ export function createLiveDeskUpdateManager({
579
647
  latestClientVersion: release.latestClientVersion,
580
648
  needsHubRestart,
581
649
  batchSize: effectiveClientBatchSize,
650
+ clientRestartStabilityMs: effectiveClientRestartStabilityMs,
582
651
  error: '',
583
652
  targets: targets.map(device => ({
584
653
  deviceId: String(device.deviceId || ''),
@@ -592,6 +661,15 @@ export function createLiveDeskUpdateManager({
592
661
  commandId: '',
593
662
  dispatchedAt: '',
594
663
  deadlineAt: '',
664
+ dispatchedSessionId: '',
665
+ dispatchedPid: 0,
666
+ candidateSessionId: '',
667
+ candidatePid: 0,
668
+ candidateConnectedAt: '',
669
+ candidateProductVersion: '',
670
+ candidateAgentVersion: '',
671
+ candidateSince: '',
672
+ stabilityDeadlineAt: '',
595
673
  completedAt: '',
596
674
  error: ''
597
675
  }))
@@ -616,10 +694,15 @@ export function createLiveDeskUpdateManager({
616
694
  runTimer.unref?.();
617
695
  verifyTargets();
618
696
  return { ok: true, ...getStatus() };
619
- };
620
-
621
- const handleRemoteEvent = (type, event) => {
622
- if (!run || run.state !== 'waiting-for-clients' || type !== 'RemoteCommandResult') return;
697
+ };
698
+
699
+ const handleRemoteEvent = (type, event) => {
700
+ if (!run || run.state !== 'waiting-for-clients') return;
701
+ if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
702
+ verifyTargets();
703
+ return;
704
+ }
705
+ if (type !== 'RemoteCommandResult') return;
623
706
  const deviceId = String(event?.device?.deviceId || '');
624
707
  const commandId = String(event?.commandId || '');
625
708
  const target = run.targets.find(item => item.deviceId === deviceId && item.commandId === commandId);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.389",
4
- "livedeskClientVersion": "0.1.172",
3
+ "version": "0.1.390",
4
+ "livedeskClientVersion": "0.1.173",
5
5
  "buildFlavor": "production",
6
6
  "description": "LiveDesk Hub and client launcher",
7
7
  "type": "module",
@@ -49,10 +49,10 @@
49
49
  "ws": "^8.18.3"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@livedesk/fast-linux-x64": "0.1.378",
53
- "@livedesk/fast-osx-arm64": "0.1.378",
54
- "@livedesk/fast-osx-x64": "0.1.378",
55
- "@livedesk/fast-win-x64": "0.1.378"
52
+ "@livedesk/fast-linux-x64": "0.1.379",
53
+ "@livedesk/fast-osx-arm64": "0.1.379",
54
+ "@livedesk/fast-osx-x64": "0.1.379",
55
+ "@livedesk/fast-win-x64": "0.1.379"
56
56
  },
57
57
  "publishConfig": {
58
58
  "access": "public"