@livedesk/client 0.1.200 → 0.1.201
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.js +295 -100
- package/package.json +5 -5
- package/src/runtime/agent-process-lifecycle.js +81 -18
package/bin/livedesk-client.js
CHANGED
|
@@ -13,7 +13,7 @@ import { formatClientVersionBanner, readClientPackageVersion } from './client-ve
|
|
|
13
13
|
import { createClientRuntimeServer } from '../src/runtime/client-runtime-server.js';
|
|
14
14
|
import {
|
|
15
15
|
createWindowsProcessTreeTracker,
|
|
16
|
-
|
|
16
|
+
drainOwnedWindowsAgentTreeUntilStopped,
|
|
17
17
|
drainOwnedUnixAgentTree,
|
|
18
18
|
installAgentTerminationHandlers,
|
|
19
19
|
signalAgentTree
|
|
@@ -33,6 +33,8 @@ const nodeAgentPath = join(__dirname, 'livedesk-client-node.js');
|
|
|
33
33
|
const FAST_PREFLIGHT_TIMEOUT_MS = 5000;
|
|
34
34
|
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
35
35
|
const DEFAULT_HUB_CLIENT_PORT = 5197;
|
|
36
|
+
const DEFAULT_RELAY_HOST = 'www.gnsoftech.com';
|
|
37
|
+
const DEFAULT_RELAY_PORT = 5199;
|
|
36
38
|
const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
|
|
37
39
|
const DEFAULT_AUTH_CALLBACK_PORT = 5179;
|
|
38
40
|
const ENDPOINT_PROBE_TIMEOUT_MS = 900;
|
|
@@ -81,7 +83,7 @@ function requestActiveAgentStop(signal = 'SIGTERM') {
|
|
|
81
83
|
// PID+CreationDate tree contract as unexpected exit and terminal
|
|
82
84
|
// shutdown. Store the shared proof promise so no replacement can race.
|
|
83
85
|
if (!child.livedeskTreeStopPromise) {
|
|
84
|
-
child.livedeskTreeStopPromise =
|
|
86
|
+
child.livedeskTreeStopPromise = drainOwnedWindowsAgentTreeUntilStopped(child);
|
|
85
87
|
}
|
|
86
88
|
return true;
|
|
87
89
|
}
|
|
@@ -213,9 +215,12 @@ Default flow:
|
|
|
213
215
|
Options:
|
|
214
216
|
--slot <number> Screen wall slot number for this computer.
|
|
215
217
|
--name <name> Friendly device name. Default: OS hostname.
|
|
216
|
-
--engine auto|fast|csharp|node Select agent engine. Default: auto.
|
|
217
|
-
--fast Force C# RemoteFast frame.binary engine.
|
|
218
|
-
--node Force the legacy Node agent.
|
|
218
|
+
--engine auto|fast|csharp|node Select agent engine. Default: auto.
|
|
219
|
+
--fast Force C# RemoteFast frame.binary engine.
|
|
220
|
+
--node Force the legacy Node agent.
|
|
221
|
+
--transport auto|tcp|ws|udp-p2p
|
|
222
|
+
Direct-first connection policy. Default: auto.
|
|
223
|
+
--relay <host:port> Encrypted fallback relay. Default: www.gnsoftech.com:5199.
|
|
219
224
|
--control Enable RemoteFast keyboard/mouse control.
|
|
220
225
|
--no-control Disable RemoteFast keyboard/mouse control.
|
|
221
226
|
--thumbnail Enable thumbnail capture when supported.
|
|
@@ -247,7 +252,7 @@ function isTruthy(value) {
|
|
|
247
252
|
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
248
253
|
}
|
|
249
254
|
|
|
250
|
-
function normalizeEngine(value) {
|
|
255
|
+
function normalizeEngine(value) {
|
|
251
256
|
const engine = String(value || 'auto').trim().toLowerCase();
|
|
252
257
|
if (engine === 'c#' || engine === 'csharp' || engine === 'fast') {
|
|
253
258
|
return 'fast';
|
|
@@ -255,11 +260,54 @@ function normalizeEngine(value) {
|
|
|
255
260
|
if (engine === 'js' || engine === 'node' || engine === 'legacy') {
|
|
256
261
|
return 'node';
|
|
257
262
|
}
|
|
258
|
-
return 'auto';
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function
|
|
262
|
-
const
|
|
263
|
+
return 'auto';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function normalizeClientTransport(value) {
|
|
267
|
+
const transport = String(value || 'auto').trim().toLowerCase();
|
|
268
|
+
if (transport === 'legacy') return 'tcp';
|
|
269
|
+
if (transport === 'wss') return 'ws';
|
|
270
|
+
if (transport === 'udp-relay') return 'udp-p2p';
|
|
271
|
+
return ['auto', 'tcp', 'ws', 'udp-p2p'].includes(transport)
|
|
272
|
+
? transport
|
|
273
|
+
: 'tcp';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function parseClientTransportOption(value) {
|
|
277
|
+
const transport = String(value || '').trim().toLowerCase();
|
|
278
|
+
if (!['auto', 'tcp', 'legacy', 'ws', 'wss', 'udp-p2p', 'udp-relay'].includes(transport)) {
|
|
279
|
+
throw new Error(`Unsupported LiveDesk Client transport: ${value || '(missing)'}`);
|
|
280
|
+
}
|
|
281
|
+
return normalizeClientTransport(transport);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function transportAllowsRelay(transport) {
|
|
285
|
+
return transport === 'auto' || transport === 'udp-p2p';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function resolveClientRelayEndpoint(env = process.env) {
|
|
289
|
+
const explicit = String(
|
|
290
|
+
env.LIVEDESK_CLIENT_RELAY
|
|
291
|
+
|| env.LIVEDESK_RELAY_ENDPOINT
|
|
292
|
+
|| ''
|
|
293
|
+
).trim().replace(/^tcp:\/\//i, '');
|
|
294
|
+
if (parseManagerEndpoint(explicit)) {
|
|
295
|
+
return explicit;
|
|
296
|
+
}
|
|
297
|
+
const host = String(
|
|
298
|
+
env.LIVEDESK_RELAY_HOST
|
|
299
|
+
|| env.LIVEDESK_UDP_RENDEZVOUS_HOST
|
|
300
|
+
|| DEFAULT_RELAY_HOST
|
|
301
|
+
).trim();
|
|
302
|
+
const port = normalizePort(
|
|
303
|
+
env.LIVEDESK_RELAY_PORT
|
|
304
|
+
|| env.LIVEDESK_UDP_RENDEZVOUS_PORT
|
|
305
|
+
) || DEFAULT_RELAY_PORT;
|
|
306
|
+
return host ? `${host}:${port}` : '';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function parseLauncherArgs(argv) {
|
|
310
|
+
const forwarded = [];
|
|
263
311
|
let engine = normalizeEngine(process.env.LIVEDESK_CLIENT_ENGINE || process.env.MINDEXEC_REMOTE_ENGINE);
|
|
264
312
|
let help = false;
|
|
265
313
|
let loginRequested = false;
|
|
@@ -267,7 +315,18 @@ function parseLauncherArgs(argv) {
|
|
|
267
315
|
let logout = false;
|
|
268
316
|
let version = false;
|
|
269
317
|
let manager = process.env.LIVEDESK_CLIENT_MANAGER || process.env.MINDEXEC_REMOTE_MANAGER || '';
|
|
270
|
-
let pair = process.env.LIVEDESK_CLIENT_PAIR_TOKEN || process.env.MINDEXEC_REMOTE_PAIR_TOKEN || '';
|
|
318
|
+
let pair = process.env.LIVEDESK_CLIENT_PAIR_TOKEN || process.env.MINDEXEC_REMOTE_PAIR_TOKEN || '';
|
|
319
|
+
let transport = normalizeClientTransport(
|
|
320
|
+
process.env.LIVEDESK_CLIENT_TRANSPORT || process.env.MINDEXEC_REMOTE_TRANSPORT);
|
|
321
|
+
let relay = resolveClientRelayEndpoint();
|
|
322
|
+
const requireFast = isTruthy(process.env.LIVEDESK_CLIENT_REQUIRE_FAST);
|
|
323
|
+
let relayExplicit = Boolean(String(
|
|
324
|
+
process.env.LIVEDESK_CLIENT_RELAY
|
|
325
|
+
|| process.env.LIVEDESK_RELAY_ENDPOINT
|
|
326
|
+
|| process.env.LIVEDESK_RELAY_HOST
|
|
327
|
+
|| process.env.LIVEDESK_UDP_RENDEZVOUS_HOST
|
|
328
|
+
|| ''
|
|
329
|
+
).trim());
|
|
271
330
|
let deviceId = String(process.env.LIVEDESK_DEVICE_ID || '').trim();
|
|
272
331
|
let slot = process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT || '';
|
|
273
332
|
let authPort = normalizePort(process.env.LIVEDESK_CLIENT_RUNTIME_PORT || process.env.LIVEDESK_CLIENT_AUTH_PORT) || DEFAULT_AUTH_CALLBACK_PORT;
|
|
@@ -347,13 +406,28 @@ function parseLauncherArgs(argv) {
|
|
|
347
406
|
index += 1;
|
|
348
407
|
continue;
|
|
349
408
|
}
|
|
350
|
-
if (arg === '--pair') {
|
|
351
|
-
pair = argv[index + 1] || pair;
|
|
352
|
-
forwarded.push(arg, argv[index + 1]);
|
|
353
|
-
index += 1;
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (arg === '--
|
|
409
|
+
if (arg === '--pair') {
|
|
410
|
+
pair = argv[index + 1] || pair;
|
|
411
|
+
forwarded.push(arg, argv[index + 1]);
|
|
412
|
+
index += 1;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (arg === '--transport') {
|
|
416
|
+
transport = parseClientTransportOption(argv[index + 1]);
|
|
417
|
+
index += 1;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (arg === '--relay') {
|
|
421
|
+
const candidate = String(argv[index + 1] || '').trim().replace(/^tcp:\/\//i, '');
|
|
422
|
+
if (!parseManagerEndpoint(candidate)) {
|
|
423
|
+
throw new Error(`Invalid LiveDesk relay endpoint: ${argv[index + 1] || '(missing)'}`);
|
|
424
|
+
}
|
|
425
|
+
relay = candidate;
|
|
426
|
+
relayExplicit = true;
|
|
427
|
+
index += 1;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (arg === '--device-id') {
|
|
357
431
|
deviceId = argv[index + 1] || deviceId;
|
|
358
432
|
forwarded.push(arg, argv[index + 1]);
|
|
359
433
|
index += 1;
|
|
@@ -393,9 +467,13 @@ function parseLauncherArgs(argv) {
|
|
|
393
467
|
loginDisabled,
|
|
394
468
|
logout,
|
|
395
469
|
version,
|
|
396
|
-
manager,
|
|
397
|
-
pair,
|
|
398
|
-
|
|
470
|
+
manager,
|
|
471
|
+
pair,
|
|
472
|
+
transport,
|
|
473
|
+
relay,
|
|
474
|
+
relayExplicit,
|
|
475
|
+
requireFast,
|
|
476
|
+
deviceId,
|
|
399
477
|
slot,
|
|
400
478
|
authPort,
|
|
401
479
|
nodeOnlyFeature,
|
|
@@ -573,13 +651,19 @@ function buildStartupClientArgs(parsed) {
|
|
|
573
651
|
if (slot) {
|
|
574
652
|
args.push(slot);
|
|
575
653
|
}
|
|
576
|
-
if (parsed.engine === 'fast') {
|
|
577
|
-
args.push('--fast');
|
|
578
|
-
} else if (parsed.engine === 'node') {
|
|
579
|
-
args.push('--node');
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
|
|
654
|
+
if (parsed.engine === 'fast') {
|
|
655
|
+
args.push('--fast');
|
|
656
|
+
} else if (parsed.engine === 'node') {
|
|
657
|
+
args.push('--node');
|
|
658
|
+
}
|
|
659
|
+
if (parsed.transport && parsed.transport !== 'auto') {
|
|
660
|
+
args.push('--transport', parsed.transport);
|
|
661
|
+
}
|
|
662
|
+
if (parsed.relayExplicit && parsed.relay) {
|
|
663
|
+
args.push('--relay', parsed.relay);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const forwarded = Array.isArray(parsed.forwarded) ? parsed.forwarded : [];
|
|
583
667
|
for (let index = 0; index < forwarded.length; index += 1) {
|
|
584
668
|
const arg = forwarded[index];
|
|
585
669
|
if (!arg || arg === 'connect') {
|
|
@@ -2471,7 +2555,7 @@ function readRequestBody(req, maxBytes = 4096) {
|
|
|
2471
2555
|
});
|
|
2472
2556
|
}
|
|
2473
2557
|
|
|
2474
|
-
async function resolveManagerFromPin(supabase, pin) {
|
|
2558
|
+
async function resolveManagerFromPin(supabase, pin, options = {}) {
|
|
2475
2559
|
const normalizedPin = normalizePairingPin(pin);
|
|
2476
2560
|
if (!normalizedPin) {
|
|
2477
2561
|
throw new Error('Enter a 6-digit LiveDesk PIN.');
|
|
@@ -2500,16 +2584,20 @@ async function resolveManagerFromPin(supabase, pin) {
|
|
|
2500
2584
|
if (endpointCandidates.length === 0) {
|
|
2501
2585
|
throw new Error('The PIN matched a Hub record without a reachable address.');
|
|
2502
2586
|
}
|
|
2503
|
-
const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2587
|
+
const target = await chooseManagerConnectionTarget(endpointCandidates, {
|
|
2588
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
2589
|
+
});
|
|
2590
|
+
if (!target) {
|
|
2591
|
+
throw new Error(`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2508
2594
|
const resolved = {
|
|
2509
|
-
manager,
|
|
2595
|
+
manager: target.manager,
|
|
2510
2596
|
pair: result.pair_token,
|
|
2511
2597
|
hubDeviceId: String(result.node_id || '').trim(),
|
|
2512
2598
|
endpointCandidates,
|
|
2599
|
+
directReachable: target.directReachable,
|
|
2600
|
+
connectionTransport: target.connectionTransport,
|
|
2513
2601
|
discoverySource: 'supabase'
|
|
2514
2602
|
};
|
|
2515
2603
|
writeCachedHubTarget(cacheOwnerKey, resolved);
|
|
@@ -2528,7 +2616,9 @@ async function waitForManagerFromSavedPin(supabase, pin, options = {}) {
|
|
|
2528
2616
|
}
|
|
2529
2617
|
attempts += 1;
|
|
2530
2618
|
try {
|
|
2531
|
-
return await resolveManagerFromPin(supabase, pin
|
|
2619
|
+
return await resolveManagerFromPin(supabase, pin, {
|
|
2620
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
2621
|
+
});
|
|
2532
2622
|
} catch (err) {
|
|
2533
2623
|
if (shouldStop()) {
|
|
2534
2624
|
return null;
|
|
@@ -2912,7 +3002,9 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
2912
3002
|
pendingStartup = normalizeStartupChoice(startupValue, pendingStartup);
|
|
2913
3003
|
pin = normalizePairingPin(pin);
|
|
2914
3004
|
try {
|
|
2915
|
-
const resolved = await resolveManagerFromPin(supabase, pin
|
|
3005
|
+
const resolved = await resolveManagerFromPin(supabase, pin, {
|
|
3006
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3007
|
+
});
|
|
2916
3008
|
writeSavedPin(pin);
|
|
2917
3009
|
applyStartupPreference(pendingStartup, startupArgs);
|
|
2918
3010
|
const choice = { type: 'pin', ...resolved };
|
|
@@ -3073,7 +3165,9 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3073
3165
|
},
|
|
3074
3166
|
resolvePin: pin => {
|
|
3075
3167
|
if (!supabase) throw new Error('supabase-session-required');
|
|
3076
|
-
return resolveManagerFromPin(supabase, pin
|
|
3168
|
+
return resolveManagerFromPin(supabase, pin, {
|
|
3169
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3170
|
+
});
|
|
3077
3171
|
},
|
|
3078
3172
|
changeRole: async (role, snapshot) => {
|
|
3079
3173
|
if (!supabase) {
|
|
@@ -3169,9 +3263,10 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3169
3263
|
deviceId: options.deviceId,
|
|
3170
3264
|
slot: options.slot,
|
|
3171
3265
|
startupArgs: options.startupArgs,
|
|
3172
|
-
savedSession: options.savedSession,
|
|
3173
|
-
savedPin: options.savedPin
|
|
3174
|
-
|
|
3266
|
+
savedSession: options.savedSession,
|
|
3267
|
+
savedPin: options.savedPin,
|
|
3268
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3269
|
+
});
|
|
3175
3270
|
if (options.openBrowser !== false) {
|
|
3176
3271
|
console.log('Opening LiveDesk connection page...');
|
|
3177
3272
|
openBrowser(connectionPage.url);
|
|
@@ -3186,11 +3281,11 @@ function parseManagerEndpoint(value) {
|
|
|
3186
3281
|
if (separator <= 0) {
|
|
3187
3282
|
return null;
|
|
3188
3283
|
}
|
|
3189
|
-
const host = text.slice(0, separator).replace(/^\[/, '').replace(/\]$/, '').trim();
|
|
3190
|
-
const port = Number(text.slice(separator + 1));
|
|
3191
|
-
if (!host || !Number.
|
|
3192
|
-
return null;
|
|
3193
|
-
}
|
|
3284
|
+
const host = text.slice(0, separator).replace(/^\[/, '').replace(/\]$/, '').trim();
|
|
3285
|
+
const port = Number(text.slice(separator + 1));
|
|
3286
|
+
if (!host || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
3287
|
+
return null;
|
|
3288
|
+
}
|
|
3194
3289
|
return { host, port };
|
|
3195
3290
|
}
|
|
3196
3291
|
|
|
@@ -3316,6 +3411,37 @@ export async function chooseReachableEndpoint(candidates, options = {}) {
|
|
|
3316
3411
|
});
|
|
3317
3412
|
}
|
|
3318
3413
|
|
|
3414
|
+
export async function chooseManagerConnectionTarget(candidates, options = {}) {
|
|
3415
|
+
const endpoints = [...new Set((Array.isArray(candidates) ? candidates : [])
|
|
3416
|
+
.map(value => String(value || '').trim())
|
|
3417
|
+
.filter(Boolean))];
|
|
3418
|
+
if (endpoints.length === 0) return null;
|
|
3419
|
+
|
|
3420
|
+
const manager = await chooseReachableEndpoint(endpoints, {
|
|
3421
|
+
requireReachable: true,
|
|
3422
|
+
probeEndpoint: options.probeEndpoint
|
|
3423
|
+
});
|
|
3424
|
+
if (manager) {
|
|
3425
|
+
return {
|
|
3426
|
+
manager,
|
|
3427
|
+
directReachable: true,
|
|
3428
|
+
connectionTransport: 'direct-tcp'
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
if (options.allowRelayFallback !== true) {
|
|
3432
|
+
return null;
|
|
3433
|
+
}
|
|
3434
|
+
return {
|
|
3435
|
+
// The account/PIN response is the authority for this retry candidate.
|
|
3436
|
+
// RemoteFast gives it one bounded direct attempt, then uses the
|
|
3437
|
+
// pair-token-authenticated encrypted relay rather than keeping the
|
|
3438
|
+
// launcher in an indefinite TCP reachability loop.
|
|
3439
|
+
manager: endpoints[0],
|
|
3440
|
+
directReachable: false,
|
|
3441
|
+
connectionTransport: 'relay-fallback'
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3319
3445
|
export async function resolveManagerFromCachedTarget(ownerKey, options = {}) {
|
|
3320
3446
|
const cached = options.cachedTarget || readCachedHubTarget(ownerKey);
|
|
3321
3447
|
if (!cached) return null;
|
|
@@ -3411,15 +3537,19 @@ async function resolveManagerFromSupabase(supabase, options = {}) {
|
|
|
3411
3537
|
if (endpointCandidates.length === 0) {
|
|
3412
3538
|
throw new Error('The LiveDesk Hub record does not contain a reachable local address.');
|
|
3413
3539
|
}
|
|
3414
|
-
const
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3540
|
+
const target = await chooseManagerConnectionTarget(endpointCandidates, {
|
|
3541
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3542
|
+
});
|
|
3543
|
+
if (!target) {
|
|
3544
|
+
throw new Error(`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`);
|
|
3545
|
+
}
|
|
3546
|
+
return {
|
|
3547
|
+
manager: target.manager,
|
|
3548
|
+
pair: data.pair_token,
|
|
3549
|
+
hubDeviceId: String(data.node_id || '').trim(),
|
|
3550
|
+
endpointCandidates,
|
|
3551
|
+
directReachable: target.directReachable,
|
|
3552
|
+
connectionTransport: target.connectionTransport
|
|
3423
3553
|
};
|
|
3424
3554
|
}
|
|
3425
3555
|
|
|
@@ -3462,7 +3592,9 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
|
|
|
3462
3592
|
}
|
|
3463
3593
|
attempts += 1;
|
|
3464
3594
|
try {
|
|
3465
|
-
return await resolveManagerFromSupabase(supabase, {
|
|
3595
|
+
return await resolveManagerFromSupabase(supabase, {
|
|
3596
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3597
|
+
});
|
|
3466
3598
|
} catch (err) {
|
|
3467
3599
|
const message = formatDiscoveryError(err);
|
|
3468
3600
|
if (message !== lastMessage || attempts === 1 || attempts % 6 === 0) {
|
|
@@ -3515,6 +3647,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3515
3647
|
let pair = parsed.pair;
|
|
3516
3648
|
let connectionPage = null;
|
|
3517
3649
|
let discoverySource = '';
|
|
3650
|
+
let connectionTransport = '';
|
|
3651
|
+
let directReachable = false;
|
|
3652
|
+
const allowRelayFallback = transportAllowsRelay(parsed.transport);
|
|
3518
3653
|
|
|
3519
3654
|
if (shouldLogin) {
|
|
3520
3655
|
const supabase = await createSupabaseClient();
|
|
@@ -3531,7 +3666,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3531
3666
|
if ((parsed.startupRun || existingConnectionPage) && savedSession?.access_token) {
|
|
3532
3667
|
choice = { type: 'google', session: savedSession };
|
|
3533
3668
|
} else if ((parsed.startupRun || existingConnectionPage) && savedPin) {
|
|
3534
|
-
const resolved = await waitForManagerFromSavedPin(supabase, savedPin
|
|
3669
|
+
const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
|
|
3670
|
+
allowRelayFallback
|
|
3671
|
+
});
|
|
3535
3672
|
choice = { type: 'pin', ...resolved };
|
|
3536
3673
|
} else if (existingConnectionPage && previousChoice) {
|
|
3537
3674
|
choice = previousChoice;
|
|
@@ -3543,10 +3680,11 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3543
3680
|
deviceId: parsed.deviceId,
|
|
3544
3681
|
engine: parsed.engine,
|
|
3545
3682
|
slot: parsed.slot,
|
|
3546
|
-
startupArgs,
|
|
3547
|
-
savedSession,
|
|
3548
|
-
savedPin,
|
|
3549
|
-
|
|
3683
|
+
startupArgs,
|
|
3684
|
+
savedSession,
|
|
3685
|
+
savedPin,
|
|
3686
|
+
allowRelayFallback,
|
|
3687
|
+
openBrowser: parsed.openBrowserOnStart
|
|
3550
3688
|
});
|
|
3551
3689
|
}
|
|
3552
3690
|
connectionPage = existingConnectionPage || choice.connectionPage || null;
|
|
@@ -3554,19 +3692,27 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3554
3692
|
manager = choice.manager;
|
|
3555
3693
|
pair = choice.pair;
|
|
3556
3694
|
discoverySource = choice.discoverySource || 'supabase';
|
|
3695
|
+
connectionTransport = choice.connectionTransport || 'direct-tcp';
|
|
3696
|
+
directReachable = choice.directReachable !== false;
|
|
3557
3697
|
connectionPage?.update({
|
|
3558
3698
|
endpointCandidates: choice.endpointCandidates || [],
|
|
3559
3699
|
manager,
|
|
3560
3700
|
assignedHubId: choice.hubDeviceId || '',
|
|
3561
|
-
message:
|
|
3562
|
-
|
|
3563
|
-
|
|
3701
|
+
message: connectionTransport === 'relay-fallback'
|
|
3702
|
+
? `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
|
|
3703
|
+
: `Connected to LiveDesk Hub at ${manager}.`
|
|
3704
|
+
});
|
|
3705
|
+
console.log(connectionTransport === 'relay-fallback'
|
|
3706
|
+
? 'LiveDesk PIN accepted. Direct TCP is unavailable; starting encrypted relay fallback.'
|
|
3707
|
+
: 'Connected by LiveDesk PIN.');
|
|
3564
3708
|
} else {
|
|
3565
3709
|
const cacheOwnerKey = hubCacheOwnerForSession(choice.session);
|
|
3566
3710
|
let resolved = await resolveManagerFromCachedTarget(cacheOwnerKey);
|
|
3567
3711
|
let session = choice.session;
|
|
3568
3712
|
discoverySource = resolved ? 'cache' : 'supabase';
|
|
3569
3713
|
if (resolved) {
|
|
3714
|
+
connectionTransport = resolved.connectionTransport || 'direct-tcp';
|
|
3715
|
+
directReachable = resolved.directReachable !== false;
|
|
3570
3716
|
writeSavedSessionToFile(session);
|
|
3571
3717
|
console.log(`Found cached LiveDesk Hub at ${resolved.manager}; skipped registry discovery.`);
|
|
3572
3718
|
} else {
|
|
@@ -3587,6 +3733,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3587
3733
|
console.log(`Signed in to LiveDesk${email}.`);
|
|
3588
3734
|
if (!resolved) {
|
|
3589
3735
|
resolved = await waitForManagerFromSupabase(supabase, {
|
|
3736
|
+
allowRelayFallback,
|
|
3590
3737
|
shouldStop: () => Boolean(roleRestartRequest?.role)
|
|
3591
3738
|
});
|
|
3592
3739
|
}
|
|
@@ -3615,15 +3762,21 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3615
3762
|
}
|
|
3616
3763
|
}
|
|
3617
3764
|
manager = resolved.manager;
|
|
3618
|
-
pair = resolved.pair;
|
|
3765
|
+
pair = resolved.pair;
|
|
3766
|
+
connectionTransport = resolved.connectionTransport || 'direct-tcp';
|
|
3767
|
+
directReachable = resolved.directReachable !== false;
|
|
3619
3768
|
connectionPage?.update({
|
|
3620
3769
|
endpointCandidates: resolved.endpointCandidates || [],
|
|
3621
3770
|
manager,
|
|
3622
3771
|
assignedHubId: resolved.hubDeviceId || '',
|
|
3623
|
-
message:
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3772
|
+
message: connectionTransport === 'relay-fallback'
|
|
3773
|
+
? `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
|
|
3774
|
+
: `Connected to LiveDesk Hub at ${manager}.`
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
console.log(connectionTransport === 'relay-fallback'
|
|
3778
|
+
? `Found LiveDesk Hub record at ${manager}; direct TCP did not answer, so RemoteFast will use the encrypted rendezvous relay.`
|
|
3779
|
+
: `Found LiveDesk Hub at ${manager}.`);
|
|
3627
3780
|
}
|
|
3628
3781
|
// Exact-package updates preserve the already paired Hub endpoint and pass
|
|
3629
3782
|
// --no-login so the replacement cannot block on browser auth. The unified
|
|
@@ -3669,12 +3822,16 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3669
3822
|
forwarded = appendForwardedFlag(forwarded, '--exit-on-invalid-pair');
|
|
3670
3823
|
}
|
|
3671
3824
|
return {
|
|
3672
|
-
...parsed,
|
|
3673
|
-
manager,
|
|
3674
|
-
pair,
|
|
3675
|
-
|
|
3825
|
+
...parsed,
|
|
3826
|
+
manager,
|
|
3827
|
+
pair,
|
|
3828
|
+
transport: parsed.transport,
|
|
3829
|
+
relay: parsed.relay,
|
|
3830
|
+
slot,
|
|
3676
3831
|
connectionPage,
|
|
3677
3832
|
forwarded,
|
|
3833
|
+
connectionTransport: connectionTransport || 'direct-tcp',
|
|
3834
|
+
directReachable: connectionTransport === 'relay-fallback' ? false : (directReachable || !shouldLogin),
|
|
3678
3835
|
discoverySource,
|
|
3679
3836
|
rediscoverOnDisconnect: shouldLogin,
|
|
3680
3837
|
rediscoverOnInvalidPair: shouldLogin
|
|
@@ -3894,7 +4051,9 @@ function buildClientAgentEnvironment(prepared) {
|
|
|
3894
4051
|
env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
|
|
3895
4052
|
env.LIVEDESK_DEVICE_ID = String(prepared?.deviceId || env.LIVEDESK_DEVICE_ID || '');
|
|
3896
4053
|
env.LIVEDESK_CLIENT_ENGINE = String(prepared?.engine || env.LIVEDESK_CLIENT_ENGINE || 'auto');
|
|
3897
|
-
env.LIVEDESK_CLIENT_TRANSPORT = String(env.LIVEDESK_CLIENT_TRANSPORT || 'auto');
|
|
4054
|
+
env.LIVEDESK_CLIENT_TRANSPORT = String(prepared?.transport || env.LIVEDESK_CLIENT_TRANSPORT || 'auto');
|
|
4055
|
+
env.LIVEDESK_CLIENT_RELAY = String(prepared?.relay || env.LIVEDESK_CLIENT_RELAY || '');
|
|
4056
|
+
env.LIVEDESK_CLIENT_REQUIRE_FAST = requiresFastTransport(prepared) ? '1' : '0';
|
|
3898
4057
|
env.LIVEDESK_UDP_ENABLED = String(env.LIVEDESK_UDP_ENABLED || '1');
|
|
3899
4058
|
env.LIVEDESK_CLIENT_UPDATE_STATE_PATH = CLIENT_UPDATE_STATE_PATH;
|
|
3900
4059
|
env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 = Buffer
|
|
@@ -4007,23 +4166,35 @@ function prepareFastExecutable(executable) {
|
|
|
4007
4166
|
}
|
|
4008
4167
|
}
|
|
4009
4168
|
|
|
4010
|
-
function buildFastArgs(args, fakeThumbnail) {
|
|
4011
|
-
const forwarded = [];
|
|
4012
|
-
for (
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4169
|
+
export function buildFastArgs(args, fakeThumbnail, transport = 'auto', relay = '') {
|
|
4170
|
+
const forwarded = [];
|
|
4171
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
4172
|
+
const arg = args[index];
|
|
4173
|
+
if (arg === '--transport' || arg === '--relay') {
|
|
4174
|
+
index += 1;
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
if (arg === '--fake-thumbnail') {
|
|
4178
|
+
forwarded.push('--fake-frame');
|
|
4179
|
+
continue;
|
|
4016
4180
|
}
|
|
4017
4181
|
if (arg === '--tasks' || arg === '--no-tasks' || arg === '--no-ai') {
|
|
4018
4182
|
continue;
|
|
4019
4183
|
}
|
|
4020
4184
|
forwarded.push(arg);
|
|
4021
4185
|
}
|
|
4022
|
-
if (fakeThumbnail && !forwarded.includes('--fake-frame')) {
|
|
4023
|
-
forwarded.push('--fake-frame');
|
|
4024
|
-
}
|
|
4025
|
-
|
|
4026
|
-
|
|
4186
|
+
if (fakeThumbnail && !forwarded.includes('--fake-frame')) {
|
|
4187
|
+
forwarded.push('--fake-frame');
|
|
4188
|
+
}
|
|
4189
|
+
let launchArgs = upsertForwardedOption(
|
|
4190
|
+
forwarded,
|
|
4191
|
+
'--transport',
|
|
4192
|
+
normalizeClientTransport(transport));
|
|
4193
|
+
if (transportAllowsRelay(normalizeClientTransport(transport))) {
|
|
4194
|
+
launchArgs = upsertForwardedOption(launchArgs, '--relay', relay);
|
|
4195
|
+
}
|
|
4196
|
+
return launchArgs;
|
|
4197
|
+
}
|
|
4027
4198
|
|
|
4028
4199
|
function redactAgentArgs(args = []) {
|
|
4029
4200
|
const redacted = [];
|
|
@@ -4169,7 +4340,7 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
|
4169
4340
|
try {
|
|
4170
4341
|
if (process.platform === 'win32') {
|
|
4171
4342
|
if (!child.livedeskTreeStopPromise) {
|
|
4172
|
-
child.livedeskTreeStopPromise =
|
|
4343
|
+
child.livedeskTreeStopPromise = drainOwnedWindowsAgentTreeUntilStopped(child);
|
|
4173
4344
|
}
|
|
4174
4345
|
const windowsTreeStop = await child.livedeskTreeStopPromise;
|
|
4175
4346
|
if (windowsTreeStop?.ok === false) {
|
|
@@ -4219,12 +4390,20 @@ function shouldUseFast(parsed) {
|
|
|
4219
4390
|
return !parsed.nodeOnlyFeature;
|
|
4220
4391
|
}
|
|
4221
4392
|
|
|
4222
|
-
function shouldTryFast(parsed, runtime) {
|
|
4393
|
+
function shouldTryFast(parsed, runtime) {
|
|
4223
4394
|
if (parsed.engine === 'fast') {
|
|
4224
4395
|
return true;
|
|
4225
4396
|
}
|
|
4226
4397
|
return shouldUseFast(parsed) && !!runtime;
|
|
4227
|
-
}
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
export function requiresFastTransport(prepared = {}) {
|
|
4401
|
+
return prepared.requireFast === true
|
|
4402
|
+
|| prepared.connectionTransport === 'relay-fallback'
|
|
4403
|
+
|| prepared.transport === 'udp-p2p'
|
|
4404
|
+
|| prepared.transport === 'ws'
|
|
4405
|
+
|| prepared.relayExplicit === true;
|
|
4406
|
+
}
|
|
4228
4407
|
|
|
4229
4408
|
export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
4230
4409
|
startNetworkChangeMonitor();
|
|
@@ -4278,9 +4457,16 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4278
4457
|
await spawnUnifiedRoleRestart(roleRestartBeforeAgent.role);
|
|
4279
4458
|
connectionPage?.close?.();
|
|
4280
4459
|
process.exit(0);
|
|
4281
|
-
}
|
|
4460
|
+
}
|
|
4282
4461
|
const fastRuntime = getFastRuntime();
|
|
4283
|
-
const
|
|
4462
|
+
const fastRequired = requiresFastTransport(prepared);
|
|
4463
|
+
if (fastRequired && prepared.engine === 'node') {
|
|
4464
|
+
throw new Error(
|
|
4465
|
+
'The legacy Node engine cannot use WebSocket, UDP P2P, or encrypted relay transport. '
|
|
4466
|
+
+ 'Use --engine fast, or use --transport tcp while the Hub is directly reachable.'
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
const useFast = fastRequired || shouldTryFast(prepared, fastRuntime);
|
|
4284
4470
|
const agentLaunchStartedAt = Date.now();
|
|
4285
4471
|
const updateAgentDashboard = (patch = {}) => {
|
|
4286
4472
|
prepared.connectionPage?.update({
|
|
@@ -4294,11 +4480,15 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4294
4480
|
? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
|
|
4295
4481
|
: 'LiveDesk client agent is launching.'
|
|
4296
4482
|
});
|
|
4297
|
-
};
|
|
4298
|
-
let result;
|
|
4299
|
-
if (useFast) {
|
|
4300
|
-
const fastArgs = buildFastArgs(
|
|
4301
|
-
|
|
4483
|
+
};
|
|
4484
|
+
let result;
|
|
4485
|
+
if (useFast) {
|
|
4486
|
+
const fastArgs = buildFastArgs(
|
|
4487
|
+
prepared.forwarded,
|
|
4488
|
+
prepared.fakeThumbnail,
|
|
4489
|
+
prepared.transport,
|
|
4490
|
+
prepared.relay);
|
|
4491
|
+
const fastLaunch = resolveFastLaunch(fastRuntime);
|
|
4302
4492
|
if (fastLaunch.ok) {
|
|
4303
4493
|
const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
|
|
4304
4494
|
updateAgentDashboard({
|
|
@@ -4319,10 +4509,15 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4319
4509
|
state: 'running'
|
|
4320
4510
|
});
|
|
4321
4511
|
});
|
|
4322
|
-
} else if (prepared.engine === 'fast') {
|
|
4323
|
-
console.error(
|
|
4324
|
-
|
|
4325
|
-
|
|
4512
|
+
} else if (prepared.engine === 'fast' || fastRequired) {
|
|
4513
|
+
console.error(
|
|
4514
|
+
`C# RemoteFast is unavailable: ${fastLaunch.reason}. `
|
|
4515
|
+
+ (fastRequired
|
|
4516
|
+
? 'The requested P2P, WebSocket, or encrypted relay transport requires the packaged RemoteFast runtime; the legacy Node agent was not started.'
|
|
4517
|
+
: 'Use --engine node to run the legacy Node agent.')
|
|
4518
|
+
);
|
|
4519
|
+
prepared.connectionPage?.close?.();
|
|
4520
|
+
process.exit(2);
|
|
4326
4521
|
} else {
|
|
4327
4522
|
console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
|
|
4328
4523
|
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.201",
|
|
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.
|
|
45
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
47
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
44
|
+
"@livedesk/fast-linux-x64": "0.1.409",
|
|
45
|
+
"@livedesk/fast-osx-arm64": "0.1.409",
|
|
46
|
+
"@livedesk/fast-osx-x64": "0.1.409",
|
|
47
|
+
"@livedesk/fast-win-x64": "0.1.409"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|
|
@@ -384,6 +384,55 @@ export async function drainOwnedWindowsAgentTree(child, {
|
|
|
384
384
|
return child.livedeskWindowsDrainPromise;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Fail-closed wrapper for lifecycle replacement and terminal shutdown.
|
|
389
|
+
* A bounded drain attempt can fail because CIM/taskkill is temporarily busy;
|
|
390
|
+
* that must never let the launcher exit and orphan an owned FFmpeg descendant.
|
|
391
|
+
*/
|
|
392
|
+
export async function drainOwnedWindowsAgentTreeUntilStopped(child, {
|
|
393
|
+
platform = process.platform,
|
|
394
|
+
retryForeverMs = 1000,
|
|
395
|
+
waitImpl = waitMilliseconds,
|
|
396
|
+
reportTerminationFailure = message => console.error(message),
|
|
397
|
+
...drainOptions
|
|
398
|
+
} = {}) {
|
|
399
|
+
if (platform !== 'win32') return { ok: true, remaining: [] };
|
|
400
|
+
if (child?.livedeskWindowsDrainUntilStoppedPromise) {
|
|
401
|
+
return child.livedeskWindowsDrainUntilStoppedPromise;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
child.livedeskWindowsDrainUntilStoppedPromise = (async () => {
|
|
405
|
+
let attempt = 0;
|
|
406
|
+
while (true) {
|
|
407
|
+
attempt += 1;
|
|
408
|
+
// drainOwnedWindowsAgentTree memoizes one bounded proof attempt.
|
|
409
|
+
// Clear only that completed attempt; the immutable tracker remains
|
|
410
|
+
// attached to the exact child and is refreshed on every retry.
|
|
411
|
+
child.livedeskWindowsDrainPromise = null;
|
|
412
|
+
let result;
|
|
413
|
+
try {
|
|
414
|
+
result = await drainOwnedWindowsAgentTree(child, {
|
|
415
|
+
platform,
|
|
416
|
+
waitImpl,
|
|
417
|
+
reportTerminationFailure,
|
|
418
|
+
...drainOptions
|
|
419
|
+
});
|
|
420
|
+
} catch (error) {
|
|
421
|
+
result = { ok: false, error, remaining: [] };
|
|
422
|
+
}
|
|
423
|
+
if (result?.ok !== false) {
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
reportTerminationFailure(
|
|
427
|
+
`[LiveDesk Client] Exact Windows Agent tree is not drained yet `
|
|
428
|
+
+ `(attempt ${attempt}); the launcher will remain alive and retry.`
|
|
429
|
+
);
|
|
430
|
+
await waitImpl(Math.max(100, Number(retryForeverMs) || 1000));
|
|
431
|
+
}
|
|
432
|
+
})();
|
|
433
|
+
return child.livedeskWindowsDrainUntilStoppedPromise;
|
|
434
|
+
}
|
|
435
|
+
|
|
387
436
|
export function isOwnedUnixProcessGroupAlive(child, platform = process.platform, signalProcess = (pid, signal) => process.kill(pid, signal)) {
|
|
388
437
|
const processId = Number(child?.pid || 0);
|
|
389
438
|
if (platform === 'win32'
|
|
@@ -513,7 +562,7 @@ export function installAgentTerminationHandlers({
|
|
|
513
562
|
shutdownTimeoutMs = 8000,
|
|
514
563
|
platform = process.platform,
|
|
515
564
|
signalProcess = (pid, signal) => process.kill(pid, signal),
|
|
516
|
-
drainWindowsTree =
|
|
565
|
+
drainWindowsTree = drainOwnedWindowsAgentTreeUntilStopped,
|
|
517
566
|
windowsTerminateAttemptLimit = 3,
|
|
518
567
|
windowsTerminateRetryMs = 100,
|
|
519
568
|
unixTerminateRetryMs = 1000,
|
|
@@ -553,12 +602,14 @@ export function installAgentTerminationHandlers({
|
|
|
553
602
|
let poll = null;
|
|
554
603
|
let timeout = null;
|
|
555
604
|
let unixRetryTimer = null;
|
|
605
|
+
let windowsRetryTimer = null;
|
|
556
606
|
const finish = () => {
|
|
557
607
|
if (finished) return;
|
|
558
608
|
finished = true;
|
|
559
609
|
if (poll) clearInterval(poll);
|
|
560
610
|
if (timeout) clearTimeout(timeout);
|
|
561
611
|
if (unixRetryTimer) clearTimeout(unixRetryTimer);
|
|
612
|
+
if (windowsRetryTimer) clearTimeout(windowsRetryTimer);
|
|
562
613
|
exitProcess(exitCode);
|
|
563
614
|
};
|
|
564
615
|
const processId = Number(child?.pid || 0);
|
|
@@ -571,25 +622,37 @@ export function installAgentTerminationHandlers({
|
|
|
571
622
|
// restarts and unexpected Agent exits. The promise is stored
|
|
572
623
|
// on the child so spawnAgent cannot race ahead and launch a
|
|
573
624
|
// replacement while terminal shutdown is still draining.
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
625
|
+
const drainUntilStopped = () => {
|
|
626
|
+
if (finished) return;
|
|
627
|
+
if (!child.livedeskTreeStopPromise) {
|
|
628
|
+
child.livedeskTreeStopPromise = Promise.resolve().then(() => drainWindowsTree(child, {
|
|
629
|
+
platform,
|
|
630
|
+
maxAttempts: windowsTerminateAttemptLimit,
|
|
631
|
+
retryMs: windowsTerminateRetryMs,
|
|
632
|
+
retryForeverMs: windowsTerminateRetryMs,
|
|
633
|
+
reportTerminationFailure
|
|
634
|
+
}));
|
|
635
|
+
}
|
|
636
|
+
child.livedeskTreeStopPromise
|
|
637
|
+
.catch(error => ({ ok: false, error }))
|
|
638
|
+
.then(result => {
|
|
639
|
+
if (result?.ok !== false) {
|
|
640
|
+
finish();
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
586
643
|
reportTerminationFailure(
|
|
587
|
-
`[LiveDesk Client] Terminal shutdown
|
|
588
|
-
+ `${result.error?.message || 'bounded drain failed'}
|
|
644
|
+
`[LiveDesk Client] Terminal shutdown retained an exact Windows Agent tree: `
|
|
645
|
+
+ `${result.error?.message || 'bounded drain failed'}. Retrying before exit.`
|
|
646
|
+
);
|
|
647
|
+
child.livedeskTreeStopPromise = null;
|
|
648
|
+
child.livedeskWindowsDrainPromise = null;
|
|
649
|
+
windowsRetryTimer = setTimeout(
|
|
650
|
+
drainUntilStopped,
|
|
651
|
+
Math.max(100, Number(windowsTerminateRetryMs) || 100)
|
|
589
652
|
);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
drainUntilStopped();
|
|
593
656
|
return;
|
|
594
657
|
}
|
|
595
658
|
const finishWhenTreeStops = () => {
|