@livedesk/client 0.1.199 → 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 +426 -125
- package/package.json +5 -5
- package/src/runtime/agent-process-lifecycle.js +600 -77
package/bin/livedesk-client.js
CHANGED
|
@@ -11,7 +11,13 @@ import net from 'node:net';
|
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import { formatClientVersionBanner, readClientPackageVersion } from './client-version.js';
|
|
13
13
|
import { createClientRuntimeServer } from '../src/runtime/client-runtime-server.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createWindowsProcessTreeTracker,
|
|
16
|
+
drainOwnedWindowsAgentTreeUntilStopped,
|
|
17
|
+
drainOwnedUnixAgentTree,
|
|
18
|
+
installAgentTerminationHandlers,
|
|
19
|
+
signalAgentTree
|
|
20
|
+
} from '../src/runtime/agent-process-lifecycle.js';
|
|
15
21
|
import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
|
|
16
22
|
import {
|
|
17
23
|
inspectLinuxVideoAcceleration,
|
|
@@ -27,6 +33,8 @@ const nodeAgentPath = join(__dirname, 'livedesk-client-node.js');
|
|
|
27
33
|
const FAST_PREFLIGHT_TIMEOUT_MS = 5000;
|
|
28
34
|
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
29
35
|
const DEFAULT_HUB_CLIENT_PORT = 5197;
|
|
36
|
+
const DEFAULT_RELAY_HOST = 'www.gnsoftech.com';
|
|
37
|
+
const DEFAULT_RELAY_PORT = 5199;
|
|
30
38
|
const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
|
|
31
39
|
const DEFAULT_AUTH_CALLBACK_PORT = 5179;
|
|
32
40
|
const ENDPOINT_PROBE_TIMEOUT_MS = 900;
|
|
@@ -67,6 +75,22 @@ let networkChangeMonitor = null;
|
|
|
67
75
|
const sessionRefreshesInFlight = new WeakMap();
|
|
68
76
|
installAgentTerminationHandlers({ getAgentProcess: () => activeAgentProcess });
|
|
69
77
|
|
|
78
|
+
function requestActiveAgentStop(signal = 'SIGTERM') {
|
|
79
|
+
const child = activeAgentProcess;
|
|
80
|
+
if (!child) return false;
|
|
81
|
+
if (process.platform === 'win32') {
|
|
82
|
+
// Internal reconnect/slot/role restarts use the same immutable
|
|
83
|
+
// PID+CreationDate tree contract as unexpected exit and terminal
|
|
84
|
+
// shutdown. Store the shared proof promise so no replacement can race.
|
|
85
|
+
if (!child.livedeskTreeStopPromise) {
|
|
86
|
+
child.livedeskTreeStopPromise = drainOwnedWindowsAgentTreeUntilStopped(child);
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
signalAgentTree(child, signal);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
export function requestLocalDiscoveryWake(reason = 'local-trigger') {
|
|
71
95
|
const previous = discoveryWakeController;
|
|
72
96
|
discoveryWakeController = new AbortController();
|
|
@@ -191,9 +215,12 @@ Default flow:
|
|
|
191
215
|
Options:
|
|
192
216
|
--slot <number> Screen wall slot number for this computer.
|
|
193
217
|
--name <name> Friendly device name. Default: OS hostname.
|
|
194
|
-
--engine auto|fast|csharp|node Select agent engine. Default: auto.
|
|
195
|
-
--fast Force C# RemoteFast frame.binary engine.
|
|
196
|
-
--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.
|
|
197
224
|
--control Enable RemoteFast keyboard/mouse control.
|
|
198
225
|
--no-control Disable RemoteFast keyboard/mouse control.
|
|
199
226
|
--thumbnail Enable thumbnail capture when supported.
|
|
@@ -225,7 +252,7 @@ function isTruthy(value) {
|
|
|
225
252
|
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
226
253
|
}
|
|
227
254
|
|
|
228
|
-
function normalizeEngine(value) {
|
|
255
|
+
function normalizeEngine(value) {
|
|
229
256
|
const engine = String(value || 'auto').trim().toLowerCase();
|
|
230
257
|
if (engine === 'c#' || engine === 'csharp' || engine === 'fast') {
|
|
231
258
|
return 'fast';
|
|
@@ -233,11 +260,54 @@ function normalizeEngine(value) {
|
|
|
233
260
|
if (engine === 'js' || engine === 'node' || engine === 'legacy') {
|
|
234
261
|
return 'node';
|
|
235
262
|
}
|
|
236
|
-
return 'auto';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function
|
|
240
|
-
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 = [];
|
|
241
311
|
let engine = normalizeEngine(process.env.LIVEDESK_CLIENT_ENGINE || process.env.MINDEXEC_REMOTE_ENGINE);
|
|
242
312
|
let help = false;
|
|
243
313
|
let loginRequested = false;
|
|
@@ -245,7 +315,18 @@ function parseLauncherArgs(argv) {
|
|
|
245
315
|
let logout = false;
|
|
246
316
|
let version = false;
|
|
247
317
|
let manager = process.env.LIVEDESK_CLIENT_MANAGER || process.env.MINDEXEC_REMOTE_MANAGER || '';
|
|
248
|
-
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());
|
|
249
330
|
let deviceId = String(process.env.LIVEDESK_DEVICE_ID || '').trim();
|
|
250
331
|
let slot = process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT || '';
|
|
251
332
|
let authPort = normalizePort(process.env.LIVEDESK_CLIENT_RUNTIME_PORT || process.env.LIVEDESK_CLIENT_AUTH_PORT) || DEFAULT_AUTH_CALLBACK_PORT;
|
|
@@ -325,13 +406,28 @@ function parseLauncherArgs(argv) {
|
|
|
325
406
|
index += 1;
|
|
326
407
|
continue;
|
|
327
408
|
}
|
|
328
|
-
if (arg === '--pair') {
|
|
329
|
-
pair = argv[index + 1] || pair;
|
|
330
|
-
forwarded.push(arg, argv[index + 1]);
|
|
331
|
-
index += 1;
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
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') {
|
|
335
431
|
deviceId = argv[index + 1] || deviceId;
|
|
336
432
|
forwarded.push(arg, argv[index + 1]);
|
|
337
433
|
index += 1;
|
|
@@ -371,9 +467,13 @@ function parseLauncherArgs(argv) {
|
|
|
371
467
|
loginDisabled,
|
|
372
468
|
logout,
|
|
373
469
|
version,
|
|
374
|
-
manager,
|
|
375
|
-
pair,
|
|
376
|
-
|
|
470
|
+
manager,
|
|
471
|
+
pair,
|
|
472
|
+
transport,
|
|
473
|
+
relay,
|
|
474
|
+
relayExplicit,
|
|
475
|
+
requireFast,
|
|
476
|
+
deviceId,
|
|
377
477
|
slot,
|
|
378
478
|
authPort,
|
|
379
479
|
nodeOnlyFeature,
|
|
@@ -551,13 +651,19 @@ function buildStartupClientArgs(parsed) {
|
|
|
551
651
|
if (slot) {
|
|
552
652
|
args.push(slot);
|
|
553
653
|
}
|
|
554
|
-
if (parsed.engine === 'fast') {
|
|
555
|
-
args.push('--fast');
|
|
556
|
-
} else if (parsed.engine === 'node') {
|
|
557
|
-
args.push('--node');
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
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 : [];
|
|
561
667
|
for (let index = 0; index < forwarded.length; index += 1) {
|
|
562
668
|
const arg = forwarded[index];
|
|
563
669
|
if (!arg || arg === 'connect') {
|
|
@@ -2449,7 +2555,7 @@ function readRequestBody(req, maxBytes = 4096) {
|
|
|
2449
2555
|
});
|
|
2450
2556
|
}
|
|
2451
2557
|
|
|
2452
|
-
async function resolveManagerFromPin(supabase, pin) {
|
|
2558
|
+
async function resolveManagerFromPin(supabase, pin, options = {}) {
|
|
2453
2559
|
const normalizedPin = normalizePairingPin(pin);
|
|
2454
2560
|
if (!normalizedPin) {
|
|
2455
2561
|
throw new Error('Enter a 6-digit LiveDesk PIN.');
|
|
@@ -2478,16 +2584,20 @@ async function resolveManagerFromPin(supabase, pin) {
|
|
|
2478
2584
|
if (endpointCandidates.length === 0) {
|
|
2479
2585
|
throw new Error('The PIN matched a Hub record without a reachable address.');
|
|
2480
2586
|
}
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
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
|
+
|
|
2486
2594
|
const resolved = {
|
|
2487
|
-
manager,
|
|
2595
|
+
manager: target.manager,
|
|
2488
2596
|
pair: result.pair_token,
|
|
2489
2597
|
hubDeviceId: String(result.node_id || '').trim(),
|
|
2490
2598
|
endpointCandidates,
|
|
2599
|
+
directReachable: target.directReachable,
|
|
2600
|
+
connectionTransport: target.connectionTransport,
|
|
2491
2601
|
discoverySource: 'supabase'
|
|
2492
2602
|
};
|
|
2493
2603
|
writeCachedHubTarget(cacheOwnerKey, resolved);
|
|
@@ -2506,7 +2616,9 @@ async function waitForManagerFromSavedPin(supabase, pin, options = {}) {
|
|
|
2506
2616
|
}
|
|
2507
2617
|
attempts += 1;
|
|
2508
2618
|
try {
|
|
2509
|
-
return await resolveManagerFromPin(supabase, pin
|
|
2619
|
+
return await resolveManagerFromPin(supabase, pin, {
|
|
2620
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
2621
|
+
});
|
|
2510
2622
|
} catch (err) {
|
|
2511
2623
|
if (shouldStop()) {
|
|
2512
2624
|
return null;
|
|
@@ -2783,7 +2895,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
2783
2895
|
res.end(JSON.stringify({ ok: true, restarting: true, role: 'hub', roleVersion: nextRoleVersion }));
|
|
2784
2896
|
setTimeout(() => {
|
|
2785
2897
|
try {
|
|
2786
|
-
|
|
2898
|
+
requestActiveAgentStop();
|
|
2787
2899
|
} catch {
|
|
2788
2900
|
}
|
|
2789
2901
|
try {
|
|
@@ -2890,7 +3002,9 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
2890
3002
|
pendingStartup = normalizeStartupChoice(startupValue, pendingStartup);
|
|
2891
3003
|
pin = normalizePairingPin(pin);
|
|
2892
3004
|
try {
|
|
2893
|
-
const resolved = await resolveManagerFromPin(supabase, pin
|
|
3005
|
+
const resolved = await resolveManagerFromPin(supabase, pin, {
|
|
3006
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3007
|
+
});
|
|
2894
3008
|
writeSavedPin(pin);
|
|
2895
3009
|
applyStartupPreference(pendingStartup, startupArgs);
|
|
2896
3010
|
const choice = { type: 'pin', ...resolved };
|
|
@@ -3051,7 +3165,9 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3051
3165
|
},
|
|
3052
3166
|
resolvePin: pin => {
|
|
3053
3167
|
if (!supabase) throw new Error('supabase-session-required');
|
|
3054
|
-
return resolveManagerFromPin(supabase, pin
|
|
3168
|
+
return resolveManagerFromPin(supabase, pin, {
|
|
3169
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3170
|
+
});
|
|
3055
3171
|
},
|
|
3056
3172
|
changeRole: async (role, snapshot) => {
|
|
3057
3173
|
if (!supabase) {
|
|
@@ -3080,7 +3196,7 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3080
3196
|
writeLocalRoleCache(role, options.deviceId, roleVersion);
|
|
3081
3197
|
roleRestartRequest = { role, requestedAt: new Date().toISOString() };
|
|
3082
3198
|
requestLocalDiscoveryWake('role-change');
|
|
3083
|
-
try {
|
|
3199
|
+
try { requestActiveAgentStop(); } catch { /* the lifecycle loop observes the role request */ }
|
|
3084
3200
|
return { ok: true, restarting: true, role, roleVersion };
|
|
3085
3201
|
},
|
|
3086
3202
|
videoAcceleration: linuxVideoAccelerationStatus,
|
|
@@ -3098,7 +3214,7 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3098
3214
|
requestedAt: new Date().toISOString()
|
|
3099
3215
|
};
|
|
3100
3216
|
requestLocalDiscoveryWake('linux-video-acceleration-ready');
|
|
3101
|
-
try {
|
|
3217
|
+
try { requestActiveAgentStop(); } catch { /* the lifecycle loop restarts with the new ffmpeg preference */ }
|
|
3102
3218
|
},
|
|
3103
3219
|
assignSlot: async request => {
|
|
3104
3220
|
const result = await requestHubSlotAssignment(request);
|
|
@@ -3113,20 +3229,20 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3113
3229
|
};
|
|
3114
3230
|
requestLocalDiscoveryWake('slot-updated');
|
|
3115
3231
|
setTimeout(() => {
|
|
3116
|
-
try {
|
|
3232
|
+
try { requestActiveAgentStop(); } catch { /* the lifecycle loop restarts with the saved slot */ }
|
|
3117
3233
|
}, 150);
|
|
3118
3234
|
return { ...result, slotNumber, saved: true, restarting: true };
|
|
3119
3235
|
},
|
|
3120
3236
|
onRestart: () => process.kill(process.pid, 'SIGTERM'),
|
|
3121
3237
|
onShutdown: () => process.kill(process.pid, 'SIGTERM'),
|
|
3122
|
-
onDisconnect: () =>
|
|
3238
|
+
onDisconnect: () => requestActiveAgentStop(),
|
|
3123
3239
|
// Ending the current Agent causes the unified lifecycle loop to
|
|
3124
3240
|
// discard its old manager/pair token and start Supabase discovery
|
|
3125
3241
|
// again. Leaving this empty made the Reconnect button a success
|
|
3126
3242
|
// response with no actual transport work.
|
|
3127
3243
|
onReconnect: () => {
|
|
3128
3244
|
requestLocalDiscoveryWake('manual-reconnect');
|
|
3129
|
-
|
|
3245
|
+
requestActiveAgentStop();
|
|
3130
3246
|
}
|
|
3131
3247
|
});
|
|
3132
3248
|
await connectionPage.start();
|
|
@@ -3147,9 +3263,10 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3147
3263
|
deviceId: options.deviceId,
|
|
3148
3264
|
slot: options.slot,
|
|
3149
3265
|
startupArgs: options.startupArgs,
|
|
3150
|
-
savedSession: options.savedSession,
|
|
3151
|
-
savedPin: options.savedPin
|
|
3152
|
-
|
|
3266
|
+
savedSession: options.savedSession,
|
|
3267
|
+
savedPin: options.savedPin,
|
|
3268
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3269
|
+
});
|
|
3153
3270
|
if (options.openBrowser !== false) {
|
|
3154
3271
|
console.log('Opening LiveDesk connection page...');
|
|
3155
3272
|
openBrowser(connectionPage.url);
|
|
@@ -3164,11 +3281,11 @@ function parseManagerEndpoint(value) {
|
|
|
3164
3281
|
if (separator <= 0) {
|
|
3165
3282
|
return null;
|
|
3166
3283
|
}
|
|
3167
|
-
const host = text.slice(0, separator).replace(/^\[/, '').replace(/\]$/, '').trim();
|
|
3168
|
-
const port = Number(text.slice(separator + 1));
|
|
3169
|
-
if (!host || !Number.
|
|
3170
|
-
return null;
|
|
3171
|
-
}
|
|
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
|
+
}
|
|
3172
3289
|
return { host, port };
|
|
3173
3290
|
}
|
|
3174
3291
|
|
|
@@ -3294,6 +3411,37 @@ export async function chooseReachableEndpoint(candidates, options = {}) {
|
|
|
3294
3411
|
});
|
|
3295
3412
|
}
|
|
3296
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
|
+
|
|
3297
3445
|
export async function resolveManagerFromCachedTarget(ownerKey, options = {}) {
|
|
3298
3446
|
const cached = options.cachedTarget || readCachedHubTarget(ownerKey);
|
|
3299
3447
|
if (!cached) return null;
|
|
@@ -3389,15 +3537,19 @@ async function resolveManagerFromSupabase(supabase, options = {}) {
|
|
|
3389
3537
|
if (endpointCandidates.length === 0) {
|
|
3390
3538
|
throw new Error('The LiveDesk Hub record does not contain a reachable local address.');
|
|
3391
3539
|
}
|
|
3392
|
-
const
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
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
|
|
3401
3553
|
};
|
|
3402
3554
|
}
|
|
3403
3555
|
|
|
@@ -3440,7 +3592,9 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
|
|
|
3440
3592
|
}
|
|
3441
3593
|
attempts += 1;
|
|
3442
3594
|
try {
|
|
3443
|
-
return await resolveManagerFromSupabase(supabase, {
|
|
3595
|
+
return await resolveManagerFromSupabase(supabase, {
|
|
3596
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3597
|
+
});
|
|
3444
3598
|
} catch (err) {
|
|
3445
3599
|
const message = formatDiscoveryError(err);
|
|
3446
3600
|
if (message !== lastMessage || attempts === 1 || attempts % 6 === 0) {
|
|
@@ -3493,6 +3647,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3493
3647
|
let pair = parsed.pair;
|
|
3494
3648
|
let connectionPage = null;
|
|
3495
3649
|
let discoverySource = '';
|
|
3650
|
+
let connectionTransport = '';
|
|
3651
|
+
let directReachable = false;
|
|
3652
|
+
const allowRelayFallback = transportAllowsRelay(parsed.transport);
|
|
3496
3653
|
|
|
3497
3654
|
if (shouldLogin) {
|
|
3498
3655
|
const supabase = await createSupabaseClient();
|
|
@@ -3509,7 +3666,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3509
3666
|
if ((parsed.startupRun || existingConnectionPage) && savedSession?.access_token) {
|
|
3510
3667
|
choice = { type: 'google', session: savedSession };
|
|
3511
3668
|
} else if ((parsed.startupRun || existingConnectionPage) && savedPin) {
|
|
3512
|
-
const resolved = await waitForManagerFromSavedPin(supabase, savedPin
|
|
3669
|
+
const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
|
|
3670
|
+
allowRelayFallback
|
|
3671
|
+
});
|
|
3513
3672
|
choice = { type: 'pin', ...resolved };
|
|
3514
3673
|
} else if (existingConnectionPage && previousChoice) {
|
|
3515
3674
|
choice = previousChoice;
|
|
@@ -3521,10 +3680,11 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3521
3680
|
deviceId: parsed.deviceId,
|
|
3522
3681
|
engine: parsed.engine,
|
|
3523
3682
|
slot: parsed.slot,
|
|
3524
|
-
startupArgs,
|
|
3525
|
-
savedSession,
|
|
3526
|
-
savedPin,
|
|
3527
|
-
|
|
3683
|
+
startupArgs,
|
|
3684
|
+
savedSession,
|
|
3685
|
+
savedPin,
|
|
3686
|
+
allowRelayFallback,
|
|
3687
|
+
openBrowser: parsed.openBrowserOnStart
|
|
3528
3688
|
});
|
|
3529
3689
|
}
|
|
3530
3690
|
connectionPage = existingConnectionPage || choice.connectionPage || null;
|
|
@@ -3532,19 +3692,27 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3532
3692
|
manager = choice.manager;
|
|
3533
3693
|
pair = choice.pair;
|
|
3534
3694
|
discoverySource = choice.discoverySource || 'supabase';
|
|
3695
|
+
connectionTransport = choice.connectionTransport || 'direct-tcp';
|
|
3696
|
+
directReachable = choice.directReachable !== false;
|
|
3535
3697
|
connectionPage?.update({
|
|
3536
3698
|
endpointCandidates: choice.endpointCandidates || [],
|
|
3537
3699
|
manager,
|
|
3538
3700
|
assignedHubId: choice.hubDeviceId || '',
|
|
3539
|
-
message:
|
|
3540
|
-
|
|
3541
|
-
|
|
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.');
|
|
3542
3708
|
} else {
|
|
3543
3709
|
const cacheOwnerKey = hubCacheOwnerForSession(choice.session);
|
|
3544
3710
|
let resolved = await resolveManagerFromCachedTarget(cacheOwnerKey);
|
|
3545
3711
|
let session = choice.session;
|
|
3546
3712
|
discoverySource = resolved ? 'cache' : 'supabase';
|
|
3547
3713
|
if (resolved) {
|
|
3714
|
+
connectionTransport = resolved.connectionTransport || 'direct-tcp';
|
|
3715
|
+
directReachable = resolved.directReachable !== false;
|
|
3548
3716
|
writeSavedSessionToFile(session);
|
|
3549
3717
|
console.log(`Found cached LiveDesk Hub at ${resolved.manager}; skipped registry discovery.`);
|
|
3550
3718
|
} else {
|
|
@@ -3565,6 +3733,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3565
3733
|
console.log(`Signed in to LiveDesk${email}.`);
|
|
3566
3734
|
if (!resolved) {
|
|
3567
3735
|
resolved = await waitForManagerFromSupabase(supabase, {
|
|
3736
|
+
allowRelayFallback,
|
|
3568
3737
|
shouldStop: () => Boolean(roleRestartRequest?.role)
|
|
3569
3738
|
});
|
|
3570
3739
|
}
|
|
@@ -3593,15 +3762,21 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3593
3762
|
}
|
|
3594
3763
|
}
|
|
3595
3764
|
manager = resolved.manager;
|
|
3596
|
-
pair = resolved.pair;
|
|
3765
|
+
pair = resolved.pair;
|
|
3766
|
+
connectionTransport = resolved.connectionTransport || 'direct-tcp';
|
|
3767
|
+
directReachable = resolved.directReachable !== false;
|
|
3597
3768
|
connectionPage?.update({
|
|
3598
3769
|
endpointCandidates: resolved.endpointCandidates || [],
|
|
3599
3770
|
manager,
|
|
3600
3771
|
assignedHubId: resolved.hubDeviceId || '',
|
|
3601
|
-
message:
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
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}.`);
|
|
3605
3780
|
}
|
|
3606
3781
|
// Exact-package updates preserve the already paired Hub endpoint and pass
|
|
3607
3782
|
// --no-login so the replacement cannot block on browser auth. The unified
|
|
@@ -3647,12 +3822,16 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3647
3822
|
forwarded = appendForwardedFlag(forwarded, '--exit-on-invalid-pair');
|
|
3648
3823
|
}
|
|
3649
3824
|
return {
|
|
3650
|
-
...parsed,
|
|
3651
|
-
manager,
|
|
3652
|
-
pair,
|
|
3653
|
-
|
|
3825
|
+
...parsed,
|
|
3826
|
+
manager,
|
|
3827
|
+
pair,
|
|
3828
|
+
transport: parsed.transport,
|
|
3829
|
+
relay: parsed.relay,
|
|
3830
|
+
slot,
|
|
3654
3831
|
connectionPage,
|
|
3655
3832
|
forwarded,
|
|
3833
|
+
connectionTransport: connectionTransport || 'direct-tcp',
|
|
3834
|
+
directReachable: connectionTransport === 'relay-fallback' ? false : (directReachable || !shouldLogin),
|
|
3656
3835
|
discoverySource,
|
|
3657
3836
|
rediscoverOnDisconnect: shouldLogin,
|
|
3658
3837
|
rediscoverOnInvalidPair: shouldLogin
|
|
@@ -3872,7 +4051,9 @@ function buildClientAgentEnvironment(prepared) {
|
|
|
3872
4051
|
env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
|
|
3873
4052
|
env.LIVEDESK_DEVICE_ID = String(prepared?.deviceId || env.LIVEDESK_DEVICE_ID || '');
|
|
3874
4053
|
env.LIVEDESK_CLIENT_ENGINE = String(prepared?.engine || env.LIVEDESK_CLIENT_ENGINE || 'auto');
|
|
3875
|
-
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';
|
|
3876
4057
|
env.LIVEDESK_UDP_ENABLED = String(env.LIVEDESK_UDP_ENABLED || '1');
|
|
3877
4058
|
env.LIVEDESK_CLIENT_UPDATE_STATE_PATH = CLIENT_UPDATE_STATE_PATH;
|
|
3878
4059
|
env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 = Buffer
|
|
@@ -3985,23 +4166,35 @@ function prepareFastExecutable(executable) {
|
|
|
3985
4166
|
}
|
|
3986
4167
|
}
|
|
3987
4168
|
|
|
3988
|
-
function buildFastArgs(args, fakeThumbnail) {
|
|
3989
|
-
const forwarded = [];
|
|
3990
|
-
for (
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
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;
|
|
3994
4180
|
}
|
|
3995
4181
|
if (arg === '--tasks' || arg === '--no-tasks' || arg === '--no-ai') {
|
|
3996
4182
|
continue;
|
|
3997
4183
|
}
|
|
3998
4184
|
forwarded.push(arg);
|
|
3999
4185
|
}
|
|
4000
|
-
if (fakeThumbnail && !forwarded.includes('--fake-frame')) {
|
|
4001
|
-
forwarded.push('--fake-frame');
|
|
4002
|
-
}
|
|
4003
|
-
|
|
4004
|
-
|
|
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
|
+
}
|
|
4005
4198
|
|
|
4006
4199
|
function redactAgentArgs(args = []) {
|
|
4007
4200
|
const redacted = [];
|
|
@@ -4103,6 +4296,7 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
|
4103
4296
|
restartVerified: false
|
|
4104
4297
|
});
|
|
4105
4298
|
const ownsProcessGroup = process.platform !== 'win32';
|
|
4299
|
+
const agentSpawnedAtMs = Date.now();
|
|
4106
4300
|
const child = spawn(command, args, {
|
|
4107
4301
|
env,
|
|
4108
4302
|
stdio: 'inherit',
|
|
@@ -4113,24 +4307,78 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
|
4113
4307
|
windowsHide: true
|
|
4114
4308
|
});
|
|
4115
4309
|
child.livedeskOwnsProcessGroup = ownsProcessGroup;
|
|
4310
|
+
child.livedeskWindowsTreeTracker = createWindowsProcessTreeTracker(child, {
|
|
4311
|
+
spawnedAtMs: agentSpawnedAtMs
|
|
4312
|
+
});
|
|
4116
4313
|
activeAgentProcess = child;
|
|
4117
4314
|
if (typeof onStart === 'function') {
|
|
4118
4315
|
onStart(child);
|
|
4119
4316
|
}
|
|
4120
4317
|
|
|
4121
|
-
return new Promise(resolve => {
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
if (activeAgentProcess === child) {
|
|
4128
|
-
activeAgentProcess = null;
|
|
4129
|
-
}
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4318
|
+
return new Promise(resolve => {
|
|
4319
|
+
let settling = false;
|
|
4320
|
+
child.once('error', error => {
|
|
4321
|
+
if (settling) return;
|
|
4322
|
+
settling = true;
|
|
4323
|
+
child.livedeskWindowsTreeTracker?.stop?.();
|
|
4324
|
+
if (activeAgentProcess === child) {
|
|
4325
|
+
activeAgentProcess = null;
|
|
4326
|
+
}
|
|
4327
|
+
console.error(`Failed to launch ${command}: ${error.message}`);
|
|
4328
|
+
resolve({ code: 1, signal: null });
|
|
4329
|
+
});
|
|
4330
|
+
child.once('exit', (code, signal) => {
|
|
4331
|
+
if (settling) return;
|
|
4332
|
+
settling = true;
|
|
4333
|
+
child.livedeskWindowsTreeTracker?.markRootExited?.();
|
|
4334
|
+
// The Unix Agent is a dedicated process-group leader. Its exit
|
|
4335
|
+
// event proves only that the leader stopped; SCK/FFmpeg capture
|
|
4336
|
+
// descendants can still own that PGID. Keep active ownership and
|
|
4337
|
+
// do not let the lifecycle loop launch a replacement until the
|
|
4338
|
+
// complete group has drained.
|
|
4339
|
+
void (async () => {
|
|
4340
|
+
try {
|
|
4341
|
+
if (process.platform === 'win32') {
|
|
4342
|
+
if (!child.livedeskTreeStopPromise) {
|
|
4343
|
+
child.livedeskTreeStopPromise = drainOwnedWindowsAgentTreeUntilStopped(child);
|
|
4344
|
+
}
|
|
4345
|
+
const windowsTreeStop = await child.livedeskTreeStopPromise;
|
|
4346
|
+
if (windowsTreeStop?.ok === false) {
|
|
4347
|
+
throw new Error(
|
|
4348
|
+
`exact Windows Agent tree drain failed: `
|
|
4349
|
+
+ `${windowsTreeStop.error?.message || 'bounded immutable-tree cleanup failed'}`
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
await drainOwnedUnixAgentTree(child);
|
|
4354
|
+
} catch (error) {
|
|
4355
|
+
console.error(
|
|
4356
|
+
`[LiveDesk Client] Could not drain exited Agent tree pid=${child.pid || 'unknown'}: `
|
|
4357
|
+
+ `${error?.message || error}`
|
|
4358
|
+
);
|
|
4359
|
+
// Block this lifecycle iteration from spawning a
|
|
4360
|
+
// replacement, but settle explicitly so the launcher
|
|
4361
|
+
// terminates instead of hanging forever.
|
|
4362
|
+
child.livedeskWindowsTreeTracker?.stop?.();
|
|
4363
|
+
if (activeAgentProcess === child) {
|
|
4364
|
+
activeAgentProcess = null;
|
|
4365
|
+
}
|
|
4366
|
+
resolve({
|
|
4367
|
+
code: 1,
|
|
4368
|
+
signal: null,
|
|
4369
|
+
lifecycleFailure: String(error?.message || error)
|
|
4370
|
+
});
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
child.livedeskWindowsTreeTracker?.stop?.();
|
|
4374
|
+
if (activeAgentProcess === child) {
|
|
4375
|
+
activeAgentProcess = null;
|
|
4376
|
+
}
|
|
4377
|
+
resolve({ code: code ?? 0, signal });
|
|
4378
|
+
})();
|
|
4379
|
+
});
|
|
4380
|
+
});
|
|
4381
|
+
}
|
|
4134
4382
|
|
|
4135
4383
|
function shouldUseFast(parsed) {
|
|
4136
4384
|
if (parsed.engine === 'node') {
|
|
@@ -4142,12 +4390,20 @@ function shouldUseFast(parsed) {
|
|
|
4142
4390
|
return !parsed.nodeOnlyFeature;
|
|
4143
4391
|
}
|
|
4144
4392
|
|
|
4145
|
-
function shouldTryFast(parsed, runtime) {
|
|
4393
|
+
function shouldTryFast(parsed, runtime) {
|
|
4146
4394
|
if (parsed.engine === 'fast') {
|
|
4147
4395
|
return true;
|
|
4148
4396
|
}
|
|
4149
4397
|
return shouldUseFast(parsed) && !!runtime;
|
|
4150
|
-
}
|
|
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
|
+
}
|
|
4151
4407
|
|
|
4152
4408
|
export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
4153
4409
|
startNetworkChangeMonitor();
|
|
@@ -4201,9 +4457,16 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4201
4457
|
await spawnUnifiedRoleRestart(roleRestartBeforeAgent.role);
|
|
4202
4458
|
connectionPage?.close?.();
|
|
4203
4459
|
process.exit(0);
|
|
4204
|
-
}
|
|
4460
|
+
}
|
|
4205
4461
|
const fastRuntime = getFastRuntime();
|
|
4206
|
-
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);
|
|
4207
4470
|
const agentLaunchStartedAt = Date.now();
|
|
4208
4471
|
const updateAgentDashboard = (patch = {}) => {
|
|
4209
4472
|
prepared.connectionPage?.update({
|
|
@@ -4217,11 +4480,15 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4217
4480
|
? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
|
|
4218
4481
|
: 'LiveDesk client agent is launching.'
|
|
4219
4482
|
});
|
|
4220
|
-
};
|
|
4221
|
-
let result;
|
|
4222
|
-
if (useFast) {
|
|
4223
|
-
const fastArgs = buildFastArgs(
|
|
4224
|
-
|
|
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);
|
|
4225
4492
|
if (fastLaunch.ok) {
|
|
4226
4493
|
const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
|
|
4227
4494
|
updateAgentDashboard({
|
|
@@ -4242,10 +4509,15 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4242
4509
|
state: 'running'
|
|
4243
4510
|
});
|
|
4244
4511
|
});
|
|
4245
|
-
} else if (prepared.engine === 'fast') {
|
|
4246
|
-
console.error(
|
|
4247
|
-
|
|
4248
|
-
|
|
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);
|
|
4249
4521
|
} else {
|
|
4250
4522
|
console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
|
|
4251
4523
|
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|
|
@@ -4286,9 +4558,36 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4286
4558
|
pid: child.pid,
|
|
4287
4559
|
startedAt: new Date().toISOString(),
|
|
4288
4560
|
state: 'running'
|
|
4289
|
-
});
|
|
4290
|
-
});
|
|
4291
|
-
}
|
|
4561
|
+
});
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
// Contract: once the owned Agent exits, the local runtime must stop
|
|
4565
|
+
// advertising its old PID before any restart or Hub rediscovery.
|
|
4566
|
+
// Keeping a dead PID makes status diagnostics lie and causes manual
|
|
4567
|
+
// reconnect to target a process that no longer exists.
|
|
4568
|
+
prepared.connectionPage?.update({
|
|
4569
|
+
agent: {
|
|
4570
|
+
requestedEngine: prepared.engine,
|
|
4571
|
+
state: 'stopped',
|
|
4572
|
+
pid: 0,
|
|
4573
|
+
stoppedAt: new Date().toISOString(),
|
|
4574
|
+
exitCode: Number.isInteger(result?.code) ? result.code : null,
|
|
4575
|
+
exitSignal: result?.signal || ''
|
|
4576
|
+
}
|
|
4577
|
+
});
|
|
4578
|
+
if (result?.lifecycleFailure) {
|
|
4579
|
+
prepared.connectionPage?.update({
|
|
4580
|
+
agent: {
|
|
4581
|
+
requestedEngine: prepared.engine,
|
|
4582
|
+
state: 'failed',
|
|
4583
|
+
pid: 0,
|
|
4584
|
+
error: result.lifecycleFailure
|
|
4585
|
+
},
|
|
4586
|
+
message: 'LiveDesk stopped because the previous Windows capture tree could not be drained safely.'
|
|
4587
|
+
});
|
|
4588
|
+
connectionPage?.close?.();
|
|
4589
|
+
throw new Error(result.lifecycleFailure);
|
|
4590
|
+
}
|
|
4292
4591
|
const requestedRoleRestart = connectionPage?.getRoleRestartRequest?.();
|
|
4293
4592
|
if (requestedRoleRestart?.role) {
|
|
4294
4593
|
if (isTruthy(process.env.LIVEDESK_DESKTOP_HOST)) process.exit(43);
|
|
@@ -4302,7 +4601,8 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4302
4601
|
connectionPage?.update({
|
|
4303
4602
|
agent: {
|
|
4304
4603
|
requestedEngine: prepared.engine,
|
|
4305
|
-
state: 'reconnecting'
|
|
4604
|
+
state: 'reconnecting',
|
|
4605
|
+
pid: 0
|
|
4306
4606
|
},
|
|
4307
4607
|
message: restart.reason === 'slot-updated'
|
|
4308
4608
|
? `Slot ${String(restart.slotNumber || prepared.slot || '').padStart(3, '0')} saved. Restarting the LiveDesk Agent.`
|
|
@@ -4331,10 +4631,11 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4331
4631
|
? 'LiveDesk Hub pair token changed. Refreshing Hub discovery now.'
|
|
4332
4632
|
: 'LiveDesk Hub connection ended. Looking for the current Hub now.');
|
|
4333
4633
|
connectionPage?.update({
|
|
4334
|
-
agent: {
|
|
4335
|
-
requestedEngine: prepared.engine,
|
|
4336
|
-
state: 'reconnecting'
|
|
4337
|
-
|
|
4634
|
+
agent: {
|
|
4635
|
+
requestedEngine: prepared.engine,
|
|
4636
|
+
state: 'reconnecting',
|
|
4637
|
+
pid: 0
|
|
4638
|
+
},
|
|
4338
4639
|
manager: '',
|
|
4339
4640
|
message: 'The previous Hub is unavailable. LiveDesk is finding the current Hub automatically.'
|
|
4340
4641
|
});
|