@livedesk/client 0.1.200 → 0.1.202
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
CHANGED
|
@@ -13,11 +13,12 @@ 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
|
|
20
20
|
} from '../src/runtime/agent-process-lifecycle.js';
|
|
21
|
+
import { writeWindowsOwnedProcessManifest } from '../src/runtime/windows-owned-process-manifest.js';
|
|
21
22
|
import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
|
|
22
23
|
import {
|
|
23
24
|
inspectLinuxVideoAcceleration,
|
|
@@ -33,6 +34,8 @@ const nodeAgentPath = join(__dirname, 'livedesk-client-node.js');
|
|
|
33
34
|
const FAST_PREFLIGHT_TIMEOUT_MS = 5000;
|
|
34
35
|
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
35
36
|
const DEFAULT_HUB_CLIENT_PORT = 5197;
|
|
37
|
+
const DEFAULT_RELAY_HOST = 'www.gnsoftech.com';
|
|
38
|
+
const DEFAULT_RELAY_PORT = 5199;
|
|
36
39
|
const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
|
|
37
40
|
const DEFAULT_AUTH_CALLBACK_PORT = 5179;
|
|
38
41
|
const ENDPOINT_PROBE_TIMEOUT_MS = 900;
|
|
@@ -71,7 +74,69 @@ let linuxVideoAccelerationStatus = null;
|
|
|
71
74
|
let discoveryWakeController = new AbortController();
|
|
72
75
|
let networkChangeMonitor = null;
|
|
73
76
|
const sessionRefreshesInFlight = new WeakMap();
|
|
74
|
-
installAgentTerminationHandlers({
|
|
77
|
+
const disposeAgentTerminationHandlers = installAgentTerminationHandlers({
|
|
78
|
+
getAgentProcess: () => activeAgentProcess
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function readWindowsManifestOwnerFromEnvironment(env = process.env) {
|
|
82
|
+
if (process.platform !== 'win32') return null;
|
|
83
|
+
const rawOwner = {
|
|
84
|
+
pid: String(env.LIVEDESK_RUNTIME_OWNER_PID || '').trim(),
|
|
85
|
+
ownerToken: String(env.LIVEDESK_RUNTIME_OWNER_TOKEN || '').trim(),
|
|
86
|
+
ownerInstanceMarker: String(env.LIVEDESK_RUNTIME_OWNER_INSTANCE_MARKER || '').trim(),
|
|
87
|
+
ownerStartOrder: String(env.LIVEDESK_RUNTIME_OWNER_START_ORDER || '').trim()
|
|
88
|
+
};
|
|
89
|
+
const hasAnyOwnerField = Object.values(rawOwner).some(Boolean);
|
|
90
|
+
if (!hasAnyOwnerField) return null;
|
|
91
|
+
const owner = {
|
|
92
|
+
...rawOwner,
|
|
93
|
+
pid: Number(rawOwner.pid)
|
|
94
|
+
};
|
|
95
|
+
if (owner.pid !== process.pid
|
|
96
|
+
|| !/^[a-f0-9]{32}$/i.test(owner.ownerToken)
|
|
97
|
+
|| !owner.ownerInstanceMarker.startsWith(`${process.pid}:`)
|
|
98
|
+
|| !/^\d+$/.test(owner.ownerStartOrder)) {
|
|
99
|
+
console.warn(
|
|
100
|
+
'[LiveDesk Client] Windows process-manifest publication is disabled because '
|
|
101
|
+
+ 'the unified launcher owner tuple is incomplete or does not name this exact process.'
|
|
102
|
+
);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return owner;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const WINDOWS_PROCESS_MANIFEST_OWNER = readWindowsManifestOwnerFromEnvironment();
|
|
109
|
+
|
|
110
|
+
function publishWindowsOwnedAgentRecords(records, {
|
|
111
|
+
rootRecord,
|
|
112
|
+
snapshot
|
|
113
|
+
} = {}) {
|
|
114
|
+
if (!WINDOWS_PROCESS_MANIFEST_OWNER || !rootRecord || records.length === 0) return;
|
|
115
|
+
const ownerRecord = (Array.isArray(snapshot) ? snapshot : []).find(
|
|
116
|
+
record => Number(record?.pid || 0) === WINDOWS_PROCESS_MANIFEST_OWNER.pid
|
|
117
|
+
);
|
|
118
|
+
const exactOwnerGeneration = ownerRecord
|
|
119
|
+
&& ownerRecord.startOrder === WINDOWS_PROCESS_MANIFEST_OWNER.ownerStartOrder
|
|
120
|
+
&& `${ownerRecord.pid}:${ownerRecord.startMarker}`
|
|
121
|
+
=== WINDOWS_PROCESS_MANIFEST_OWNER.ownerInstanceMarker;
|
|
122
|
+
if (!exactOwnerGeneration) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
'The Windows process snapshot did not confirm the unified launcher owner generation.'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const publication = writeWindowsOwnedProcessManifest({
|
|
128
|
+
stateDir: UNIFIED_CLIENT_STATE_DIR,
|
|
129
|
+
owner: WINDOWS_PROCESS_MANIFEST_OWNER,
|
|
130
|
+
records
|
|
131
|
+
});
|
|
132
|
+
if (!publication.ok) {
|
|
133
|
+
throw publication.error || new Error('Windows owned-process manifest publication failed.');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function requestClientRuntimeShutdown(signal = 'SIGTERM') {
|
|
138
|
+
disposeAgentTerminationHandlers.requestTermination(signal);
|
|
139
|
+
}
|
|
75
140
|
|
|
76
141
|
function requestActiveAgentStop(signal = 'SIGTERM') {
|
|
77
142
|
const child = activeAgentProcess;
|
|
@@ -81,7 +146,7 @@ function requestActiveAgentStop(signal = 'SIGTERM') {
|
|
|
81
146
|
// PID+CreationDate tree contract as unexpected exit and terminal
|
|
82
147
|
// shutdown. Store the shared proof promise so no replacement can race.
|
|
83
148
|
if (!child.livedeskTreeStopPromise) {
|
|
84
|
-
child.livedeskTreeStopPromise =
|
|
149
|
+
child.livedeskTreeStopPromise = drainOwnedWindowsAgentTreeUntilStopped(child);
|
|
85
150
|
}
|
|
86
151
|
return true;
|
|
87
152
|
}
|
|
@@ -213,9 +278,12 @@ Default flow:
|
|
|
213
278
|
Options:
|
|
214
279
|
--slot <number> Screen wall slot number for this computer.
|
|
215
280
|
--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.
|
|
281
|
+
--engine auto|fast|csharp|node Select agent engine. Default: auto.
|
|
282
|
+
--fast Force C# RemoteFast frame.binary engine.
|
|
283
|
+
--node Force the legacy Node agent.
|
|
284
|
+
--transport auto|tcp|ws|udp-p2p
|
|
285
|
+
Direct-first connection policy. Default: auto.
|
|
286
|
+
--relay <host:port> Encrypted fallback relay. Default: www.gnsoftech.com:5199.
|
|
219
287
|
--control Enable RemoteFast keyboard/mouse control.
|
|
220
288
|
--no-control Disable RemoteFast keyboard/mouse control.
|
|
221
289
|
--thumbnail Enable thumbnail capture when supported.
|
|
@@ -247,7 +315,7 @@ function isTruthy(value) {
|
|
|
247
315
|
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
248
316
|
}
|
|
249
317
|
|
|
250
|
-
function normalizeEngine(value) {
|
|
318
|
+
function normalizeEngine(value) {
|
|
251
319
|
const engine = String(value || 'auto').trim().toLowerCase();
|
|
252
320
|
if (engine === 'c#' || engine === 'csharp' || engine === 'fast') {
|
|
253
321
|
return 'fast';
|
|
@@ -255,11 +323,54 @@ function normalizeEngine(value) {
|
|
|
255
323
|
if (engine === 'js' || engine === 'node' || engine === 'legacy') {
|
|
256
324
|
return 'node';
|
|
257
325
|
}
|
|
258
|
-
return 'auto';
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function
|
|
262
|
-
const
|
|
326
|
+
return 'auto';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function normalizeClientTransport(value) {
|
|
330
|
+
const transport = String(value || 'auto').trim().toLowerCase();
|
|
331
|
+
if (transport === 'legacy') return 'tcp';
|
|
332
|
+
if (transport === 'wss') return 'ws';
|
|
333
|
+
if (transport === 'udp-relay') return 'udp-p2p';
|
|
334
|
+
return ['auto', 'tcp', 'ws', 'udp-p2p'].includes(transport)
|
|
335
|
+
? transport
|
|
336
|
+
: 'tcp';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function parseClientTransportOption(value) {
|
|
340
|
+
const transport = String(value || '').trim().toLowerCase();
|
|
341
|
+
if (!['auto', 'tcp', 'legacy', 'ws', 'wss', 'udp-p2p', 'udp-relay'].includes(transport)) {
|
|
342
|
+
throw new Error(`Unsupported LiveDesk Client transport: ${value || '(missing)'}`);
|
|
343
|
+
}
|
|
344
|
+
return normalizeClientTransport(transport);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function transportAllowsRelay(transport) {
|
|
348
|
+
return transport === 'auto' || transport === 'udp-p2p';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function resolveClientRelayEndpoint(env = process.env) {
|
|
352
|
+
const explicit = String(
|
|
353
|
+
env.LIVEDESK_CLIENT_RELAY
|
|
354
|
+
|| env.LIVEDESK_RELAY_ENDPOINT
|
|
355
|
+
|| ''
|
|
356
|
+
).trim().replace(/^tcp:\/\//i, '');
|
|
357
|
+
if (parseManagerEndpoint(explicit)) {
|
|
358
|
+
return explicit;
|
|
359
|
+
}
|
|
360
|
+
const host = String(
|
|
361
|
+
env.LIVEDESK_RELAY_HOST
|
|
362
|
+
|| env.LIVEDESK_UDP_RENDEZVOUS_HOST
|
|
363
|
+
|| DEFAULT_RELAY_HOST
|
|
364
|
+
).trim();
|
|
365
|
+
const port = normalizePort(
|
|
366
|
+
env.LIVEDESK_RELAY_PORT
|
|
367
|
+
|| env.LIVEDESK_UDP_RENDEZVOUS_PORT
|
|
368
|
+
) || DEFAULT_RELAY_PORT;
|
|
369
|
+
return host ? `${host}:${port}` : '';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function parseLauncherArgs(argv) {
|
|
373
|
+
const forwarded = [];
|
|
263
374
|
let engine = normalizeEngine(process.env.LIVEDESK_CLIENT_ENGINE || process.env.MINDEXEC_REMOTE_ENGINE);
|
|
264
375
|
let help = false;
|
|
265
376
|
let loginRequested = false;
|
|
@@ -267,7 +378,18 @@ function parseLauncherArgs(argv) {
|
|
|
267
378
|
let logout = false;
|
|
268
379
|
let version = false;
|
|
269
380
|
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 || '';
|
|
381
|
+
let pair = process.env.LIVEDESK_CLIENT_PAIR_TOKEN || process.env.MINDEXEC_REMOTE_PAIR_TOKEN || '';
|
|
382
|
+
let transport = normalizeClientTransport(
|
|
383
|
+
process.env.LIVEDESK_CLIENT_TRANSPORT || process.env.MINDEXEC_REMOTE_TRANSPORT);
|
|
384
|
+
let relay = resolveClientRelayEndpoint();
|
|
385
|
+
const requireFast = isTruthy(process.env.LIVEDESK_CLIENT_REQUIRE_FAST);
|
|
386
|
+
let relayExplicit = Boolean(String(
|
|
387
|
+
process.env.LIVEDESK_CLIENT_RELAY
|
|
388
|
+
|| process.env.LIVEDESK_RELAY_ENDPOINT
|
|
389
|
+
|| process.env.LIVEDESK_RELAY_HOST
|
|
390
|
+
|| process.env.LIVEDESK_UDP_RENDEZVOUS_HOST
|
|
391
|
+
|| ''
|
|
392
|
+
).trim());
|
|
271
393
|
let deviceId = String(process.env.LIVEDESK_DEVICE_ID || '').trim();
|
|
272
394
|
let slot = process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT || '';
|
|
273
395
|
let authPort = normalizePort(process.env.LIVEDESK_CLIENT_RUNTIME_PORT || process.env.LIVEDESK_CLIENT_AUTH_PORT) || DEFAULT_AUTH_CALLBACK_PORT;
|
|
@@ -347,13 +469,28 @@ function parseLauncherArgs(argv) {
|
|
|
347
469
|
index += 1;
|
|
348
470
|
continue;
|
|
349
471
|
}
|
|
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 === '--
|
|
472
|
+
if (arg === '--pair') {
|
|
473
|
+
pair = argv[index + 1] || pair;
|
|
474
|
+
forwarded.push(arg, argv[index + 1]);
|
|
475
|
+
index += 1;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (arg === '--transport') {
|
|
479
|
+
transport = parseClientTransportOption(argv[index + 1]);
|
|
480
|
+
index += 1;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (arg === '--relay') {
|
|
484
|
+
const candidate = String(argv[index + 1] || '').trim().replace(/^tcp:\/\//i, '');
|
|
485
|
+
if (!parseManagerEndpoint(candidate)) {
|
|
486
|
+
throw new Error(`Invalid LiveDesk relay endpoint: ${argv[index + 1] || '(missing)'}`);
|
|
487
|
+
}
|
|
488
|
+
relay = candidate;
|
|
489
|
+
relayExplicit = true;
|
|
490
|
+
index += 1;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (arg === '--device-id') {
|
|
357
494
|
deviceId = argv[index + 1] || deviceId;
|
|
358
495
|
forwarded.push(arg, argv[index + 1]);
|
|
359
496
|
index += 1;
|
|
@@ -393,9 +530,13 @@ function parseLauncherArgs(argv) {
|
|
|
393
530
|
loginDisabled,
|
|
394
531
|
logout,
|
|
395
532
|
version,
|
|
396
|
-
manager,
|
|
397
|
-
pair,
|
|
398
|
-
|
|
533
|
+
manager,
|
|
534
|
+
pair,
|
|
535
|
+
transport,
|
|
536
|
+
relay,
|
|
537
|
+
relayExplicit,
|
|
538
|
+
requireFast,
|
|
539
|
+
deviceId,
|
|
399
540
|
slot,
|
|
400
541
|
authPort,
|
|
401
542
|
nodeOnlyFeature,
|
|
@@ -573,13 +714,19 @@ function buildStartupClientArgs(parsed) {
|
|
|
573
714
|
if (slot) {
|
|
574
715
|
args.push(slot);
|
|
575
716
|
}
|
|
576
|
-
if (parsed.engine === 'fast') {
|
|
577
|
-
args.push('--fast');
|
|
578
|
-
} else if (parsed.engine === 'node') {
|
|
579
|
-
args.push('--node');
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
|
|
717
|
+
if (parsed.engine === 'fast') {
|
|
718
|
+
args.push('--fast');
|
|
719
|
+
} else if (parsed.engine === 'node') {
|
|
720
|
+
args.push('--node');
|
|
721
|
+
}
|
|
722
|
+
if (parsed.transport && parsed.transport !== 'auto') {
|
|
723
|
+
args.push('--transport', parsed.transport);
|
|
724
|
+
}
|
|
725
|
+
if (parsed.relayExplicit && parsed.relay) {
|
|
726
|
+
args.push('--relay', parsed.relay);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const forwarded = Array.isArray(parsed.forwarded) ? parsed.forwarded : [];
|
|
583
730
|
for (let index = 0; index < forwarded.length; index += 1) {
|
|
584
731
|
const arg = forwarded[index];
|
|
585
732
|
if (!arg || arg === 'connect') {
|
|
@@ -2471,7 +2618,7 @@ function readRequestBody(req, maxBytes = 4096) {
|
|
|
2471
2618
|
});
|
|
2472
2619
|
}
|
|
2473
2620
|
|
|
2474
|
-
async function resolveManagerFromPin(supabase, pin) {
|
|
2621
|
+
async function resolveManagerFromPin(supabase, pin, options = {}) {
|
|
2475
2622
|
const normalizedPin = normalizePairingPin(pin);
|
|
2476
2623
|
if (!normalizedPin) {
|
|
2477
2624
|
throw new Error('Enter a 6-digit LiveDesk PIN.');
|
|
@@ -2500,16 +2647,20 @@ async function resolveManagerFromPin(supabase, pin) {
|
|
|
2500
2647
|
if (endpointCandidates.length === 0) {
|
|
2501
2648
|
throw new Error('The PIN matched a Hub record without a reachable address.');
|
|
2502
2649
|
}
|
|
2503
|
-
const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2650
|
+
const target = await chooseManagerConnectionTarget(endpointCandidates, {
|
|
2651
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
2652
|
+
});
|
|
2653
|
+
if (!target) {
|
|
2654
|
+
throw new Error(`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2508
2657
|
const resolved = {
|
|
2509
|
-
manager,
|
|
2658
|
+
manager: target.manager,
|
|
2510
2659
|
pair: result.pair_token,
|
|
2511
2660
|
hubDeviceId: String(result.node_id || '').trim(),
|
|
2512
2661
|
endpointCandidates,
|
|
2662
|
+
directReachable: target.directReachable,
|
|
2663
|
+
connectionTransport: target.connectionTransport,
|
|
2513
2664
|
discoverySource: 'supabase'
|
|
2514
2665
|
};
|
|
2515
2666
|
writeCachedHubTarget(cacheOwnerKey, resolved);
|
|
@@ -2528,7 +2679,9 @@ async function waitForManagerFromSavedPin(supabase, pin, options = {}) {
|
|
|
2528
2679
|
}
|
|
2529
2680
|
attempts += 1;
|
|
2530
2681
|
try {
|
|
2531
|
-
return await resolveManagerFromPin(supabase, pin
|
|
2682
|
+
return await resolveManagerFromPin(supabase, pin, {
|
|
2683
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
2684
|
+
});
|
|
2532
2685
|
} catch (err) {
|
|
2533
2686
|
if (shouldStop()) {
|
|
2534
2687
|
return null;
|
|
@@ -2912,7 +3065,9 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
2912
3065
|
pendingStartup = normalizeStartupChoice(startupValue, pendingStartup);
|
|
2913
3066
|
pin = normalizePairingPin(pin);
|
|
2914
3067
|
try {
|
|
2915
|
-
const resolved = await resolveManagerFromPin(supabase, pin
|
|
3068
|
+
const resolved = await resolveManagerFromPin(supabase, pin, {
|
|
3069
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3070
|
+
});
|
|
2916
3071
|
writeSavedPin(pin);
|
|
2917
3072
|
applyStartupPreference(pendingStartup, startupArgs);
|
|
2918
3073
|
const choice = { type: 'pin', ...resolved };
|
|
@@ -3073,7 +3228,9 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3073
3228
|
},
|
|
3074
3229
|
resolvePin: pin => {
|
|
3075
3230
|
if (!supabase) throw new Error('supabase-session-required');
|
|
3076
|
-
return resolveManagerFromPin(supabase, pin
|
|
3231
|
+
return resolveManagerFromPin(supabase, pin, {
|
|
3232
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3233
|
+
});
|
|
3077
3234
|
},
|
|
3078
3235
|
changeRole: async (role, snapshot) => {
|
|
3079
3236
|
if (!supabase) {
|
|
@@ -3139,8 +3296,8 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3139
3296
|
}, 150);
|
|
3140
3297
|
return { ...result, slotNumber, saved: true, restarting: true };
|
|
3141
3298
|
},
|
|
3142
|
-
onRestart: () =>
|
|
3143
|
-
onShutdown: () =>
|
|
3299
|
+
onRestart: () => requestClientRuntimeShutdown('SIGTERM'),
|
|
3300
|
+
onShutdown: () => requestClientRuntimeShutdown('SIGTERM'),
|
|
3144
3301
|
onDisconnect: () => requestActiveAgentStop(),
|
|
3145
3302
|
// Ending the current Agent causes the unified lifecycle loop to
|
|
3146
3303
|
// discard its old manager/pair token and start Supabase discovery
|
|
@@ -3169,9 +3326,10 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3169
3326
|
deviceId: options.deviceId,
|
|
3170
3327
|
slot: options.slot,
|
|
3171
3328
|
startupArgs: options.startupArgs,
|
|
3172
|
-
savedSession: options.savedSession,
|
|
3173
|
-
savedPin: options.savedPin
|
|
3174
|
-
|
|
3329
|
+
savedSession: options.savedSession,
|
|
3330
|
+
savedPin: options.savedPin,
|
|
3331
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3332
|
+
});
|
|
3175
3333
|
if (options.openBrowser !== false) {
|
|
3176
3334
|
console.log('Opening LiveDesk connection page...');
|
|
3177
3335
|
openBrowser(connectionPage.url);
|
|
@@ -3186,11 +3344,11 @@ function parseManagerEndpoint(value) {
|
|
|
3186
3344
|
if (separator <= 0) {
|
|
3187
3345
|
return null;
|
|
3188
3346
|
}
|
|
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
|
-
}
|
|
3347
|
+
const host = text.slice(0, separator).replace(/^\[/, '').replace(/\]$/, '').trim();
|
|
3348
|
+
const port = Number(text.slice(separator + 1));
|
|
3349
|
+
if (!host || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
3350
|
+
return null;
|
|
3351
|
+
}
|
|
3194
3352
|
return { host, port };
|
|
3195
3353
|
}
|
|
3196
3354
|
|
|
@@ -3316,6 +3474,37 @@ export async function chooseReachableEndpoint(candidates, options = {}) {
|
|
|
3316
3474
|
});
|
|
3317
3475
|
}
|
|
3318
3476
|
|
|
3477
|
+
export async function chooseManagerConnectionTarget(candidates, options = {}) {
|
|
3478
|
+
const endpoints = [...new Set((Array.isArray(candidates) ? candidates : [])
|
|
3479
|
+
.map(value => String(value || '').trim())
|
|
3480
|
+
.filter(Boolean))];
|
|
3481
|
+
if (endpoints.length === 0) return null;
|
|
3482
|
+
|
|
3483
|
+
const manager = await chooseReachableEndpoint(endpoints, {
|
|
3484
|
+
requireReachable: true,
|
|
3485
|
+
probeEndpoint: options.probeEndpoint
|
|
3486
|
+
});
|
|
3487
|
+
if (manager) {
|
|
3488
|
+
return {
|
|
3489
|
+
manager,
|
|
3490
|
+
directReachable: true,
|
|
3491
|
+
connectionTransport: 'direct-tcp'
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
if (options.allowRelayFallback !== true) {
|
|
3495
|
+
return null;
|
|
3496
|
+
}
|
|
3497
|
+
return {
|
|
3498
|
+
// The account/PIN response is the authority for this retry candidate.
|
|
3499
|
+
// RemoteFast gives it one bounded direct attempt, then uses the
|
|
3500
|
+
// pair-token-authenticated encrypted relay rather than keeping the
|
|
3501
|
+
// launcher in an indefinite TCP reachability loop.
|
|
3502
|
+
manager: endpoints[0],
|
|
3503
|
+
directReachable: false,
|
|
3504
|
+
connectionTransport: 'relay-fallback'
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3319
3508
|
export async function resolveManagerFromCachedTarget(ownerKey, options = {}) {
|
|
3320
3509
|
const cached = options.cachedTarget || readCachedHubTarget(ownerKey);
|
|
3321
3510
|
if (!cached) return null;
|
|
@@ -3411,15 +3600,19 @@ async function resolveManagerFromSupabase(supabase, options = {}) {
|
|
|
3411
3600
|
if (endpointCandidates.length === 0) {
|
|
3412
3601
|
throw new Error('The LiveDesk Hub record does not contain a reachable local address.');
|
|
3413
3602
|
}
|
|
3414
|
-
const
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3603
|
+
const target = await chooseManagerConnectionTarget(endpointCandidates, {
|
|
3604
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3605
|
+
});
|
|
3606
|
+
if (!target) {
|
|
3607
|
+
throw new Error(`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`);
|
|
3608
|
+
}
|
|
3609
|
+
return {
|
|
3610
|
+
manager: target.manager,
|
|
3611
|
+
pair: data.pair_token,
|
|
3612
|
+
hubDeviceId: String(data.node_id || '').trim(),
|
|
3613
|
+
endpointCandidates,
|
|
3614
|
+
directReachable: target.directReachable,
|
|
3615
|
+
connectionTransport: target.connectionTransport
|
|
3423
3616
|
};
|
|
3424
3617
|
}
|
|
3425
3618
|
|
|
@@ -3462,7 +3655,9 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
|
|
|
3462
3655
|
}
|
|
3463
3656
|
attempts += 1;
|
|
3464
3657
|
try {
|
|
3465
|
-
return await resolveManagerFromSupabase(supabase, {
|
|
3658
|
+
return await resolveManagerFromSupabase(supabase, {
|
|
3659
|
+
allowRelayFallback: options.allowRelayFallback === true
|
|
3660
|
+
});
|
|
3466
3661
|
} catch (err) {
|
|
3467
3662
|
const message = formatDiscoveryError(err);
|
|
3468
3663
|
if (message !== lastMessage || attempts === 1 || attempts % 6 === 0) {
|
|
@@ -3515,6 +3710,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3515
3710
|
let pair = parsed.pair;
|
|
3516
3711
|
let connectionPage = null;
|
|
3517
3712
|
let discoverySource = '';
|
|
3713
|
+
let connectionTransport = '';
|
|
3714
|
+
let directReachable = false;
|
|
3715
|
+
const allowRelayFallback = transportAllowsRelay(parsed.transport);
|
|
3518
3716
|
|
|
3519
3717
|
if (shouldLogin) {
|
|
3520
3718
|
const supabase = await createSupabaseClient();
|
|
@@ -3531,7 +3729,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3531
3729
|
if ((parsed.startupRun || existingConnectionPage) && savedSession?.access_token) {
|
|
3532
3730
|
choice = { type: 'google', session: savedSession };
|
|
3533
3731
|
} else if ((parsed.startupRun || existingConnectionPage) && savedPin) {
|
|
3534
|
-
const resolved = await waitForManagerFromSavedPin(supabase, savedPin
|
|
3732
|
+
const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
|
|
3733
|
+
allowRelayFallback
|
|
3734
|
+
});
|
|
3535
3735
|
choice = { type: 'pin', ...resolved };
|
|
3536
3736
|
} else if (existingConnectionPage && previousChoice) {
|
|
3537
3737
|
choice = previousChoice;
|
|
@@ -3543,10 +3743,11 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3543
3743
|
deviceId: parsed.deviceId,
|
|
3544
3744
|
engine: parsed.engine,
|
|
3545
3745
|
slot: parsed.slot,
|
|
3546
|
-
startupArgs,
|
|
3547
|
-
savedSession,
|
|
3548
|
-
savedPin,
|
|
3549
|
-
|
|
3746
|
+
startupArgs,
|
|
3747
|
+
savedSession,
|
|
3748
|
+
savedPin,
|
|
3749
|
+
allowRelayFallback,
|
|
3750
|
+
openBrowser: parsed.openBrowserOnStart
|
|
3550
3751
|
});
|
|
3551
3752
|
}
|
|
3552
3753
|
connectionPage = existingConnectionPage || choice.connectionPage || null;
|
|
@@ -3554,19 +3755,27 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3554
3755
|
manager = choice.manager;
|
|
3555
3756
|
pair = choice.pair;
|
|
3556
3757
|
discoverySource = choice.discoverySource || 'supabase';
|
|
3758
|
+
connectionTransport = choice.connectionTransport || 'direct-tcp';
|
|
3759
|
+
directReachable = choice.directReachable !== false;
|
|
3557
3760
|
connectionPage?.update({
|
|
3558
3761
|
endpointCandidates: choice.endpointCandidates || [],
|
|
3559
3762
|
manager,
|
|
3560
3763
|
assignedHubId: choice.hubDeviceId || '',
|
|
3561
|
-
message:
|
|
3562
|
-
|
|
3563
|
-
|
|
3764
|
+
message: connectionTransport === 'relay-fallback'
|
|
3765
|
+
? `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
|
|
3766
|
+
: `Connected to LiveDesk Hub at ${manager}.`
|
|
3767
|
+
});
|
|
3768
|
+
console.log(connectionTransport === 'relay-fallback'
|
|
3769
|
+
? 'LiveDesk PIN accepted. Direct TCP is unavailable; starting encrypted relay fallback.'
|
|
3770
|
+
: 'Connected by LiveDesk PIN.');
|
|
3564
3771
|
} else {
|
|
3565
3772
|
const cacheOwnerKey = hubCacheOwnerForSession(choice.session);
|
|
3566
3773
|
let resolved = await resolveManagerFromCachedTarget(cacheOwnerKey);
|
|
3567
3774
|
let session = choice.session;
|
|
3568
3775
|
discoverySource = resolved ? 'cache' : 'supabase';
|
|
3569
3776
|
if (resolved) {
|
|
3777
|
+
connectionTransport = resolved.connectionTransport || 'direct-tcp';
|
|
3778
|
+
directReachable = resolved.directReachable !== false;
|
|
3570
3779
|
writeSavedSessionToFile(session);
|
|
3571
3780
|
console.log(`Found cached LiveDesk Hub at ${resolved.manager}; skipped registry discovery.`);
|
|
3572
3781
|
} else {
|
|
@@ -3587,6 +3796,7 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3587
3796
|
console.log(`Signed in to LiveDesk${email}.`);
|
|
3588
3797
|
if (!resolved) {
|
|
3589
3798
|
resolved = await waitForManagerFromSupabase(supabase, {
|
|
3799
|
+
allowRelayFallback,
|
|
3590
3800
|
shouldStop: () => Boolean(roleRestartRequest?.role)
|
|
3591
3801
|
});
|
|
3592
3802
|
}
|
|
@@ -3615,15 +3825,21 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3615
3825
|
}
|
|
3616
3826
|
}
|
|
3617
3827
|
manager = resolved.manager;
|
|
3618
|
-
pair = resolved.pair;
|
|
3828
|
+
pair = resolved.pair;
|
|
3829
|
+
connectionTransport = resolved.connectionTransport || 'direct-tcp';
|
|
3830
|
+
directReachable = resolved.directReachable !== false;
|
|
3619
3831
|
connectionPage?.update({
|
|
3620
3832
|
endpointCandidates: resolved.endpointCandidates || [],
|
|
3621
3833
|
manager,
|
|
3622
3834
|
assignedHubId: resolved.hubDeviceId || '',
|
|
3623
|
-
message:
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3835
|
+
message: connectionTransport === 'relay-fallback'
|
|
3836
|
+
? `LiveDesk Hub found at ${manager}; starting encrypted relay fallback.`
|
|
3837
|
+
: `Connected to LiveDesk Hub at ${manager}.`
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
console.log(connectionTransport === 'relay-fallback'
|
|
3841
|
+
? `Found LiveDesk Hub record at ${manager}; direct TCP did not answer, so RemoteFast will use the encrypted rendezvous relay.`
|
|
3842
|
+
: `Found LiveDesk Hub at ${manager}.`);
|
|
3627
3843
|
}
|
|
3628
3844
|
// Exact-package updates preserve the already paired Hub endpoint and pass
|
|
3629
3845
|
// --no-login so the replacement cannot block on browser auth. The unified
|
|
@@ -3669,12 +3885,16 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3669
3885
|
forwarded = appendForwardedFlag(forwarded, '--exit-on-invalid-pair');
|
|
3670
3886
|
}
|
|
3671
3887
|
return {
|
|
3672
|
-
...parsed,
|
|
3673
|
-
manager,
|
|
3674
|
-
pair,
|
|
3675
|
-
|
|
3888
|
+
...parsed,
|
|
3889
|
+
manager,
|
|
3890
|
+
pair,
|
|
3891
|
+
transport: parsed.transport,
|
|
3892
|
+
relay: parsed.relay,
|
|
3893
|
+
slot,
|
|
3676
3894
|
connectionPage,
|
|
3677
3895
|
forwarded,
|
|
3896
|
+
connectionTransport: connectionTransport || 'direct-tcp',
|
|
3897
|
+
directReachable: connectionTransport === 'relay-fallback' ? false : (directReachable || !shouldLogin),
|
|
3678
3898
|
discoverySource,
|
|
3679
3899
|
rediscoverOnDisconnect: shouldLogin,
|
|
3680
3900
|
rediscoverOnInvalidPair: shouldLogin
|
|
@@ -3894,7 +4114,9 @@ function buildClientAgentEnvironment(prepared) {
|
|
|
3894
4114
|
env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
|
|
3895
4115
|
env.LIVEDESK_DEVICE_ID = String(prepared?.deviceId || env.LIVEDESK_DEVICE_ID || '');
|
|
3896
4116
|
env.LIVEDESK_CLIENT_ENGINE = String(prepared?.engine || env.LIVEDESK_CLIENT_ENGINE || 'auto');
|
|
3897
|
-
env.LIVEDESK_CLIENT_TRANSPORT = String(env.LIVEDESK_CLIENT_TRANSPORT || 'auto');
|
|
4117
|
+
env.LIVEDESK_CLIENT_TRANSPORT = String(prepared?.transport || env.LIVEDESK_CLIENT_TRANSPORT || 'auto');
|
|
4118
|
+
env.LIVEDESK_CLIENT_RELAY = String(prepared?.relay || env.LIVEDESK_CLIENT_RELAY || '');
|
|
4119
|
+
env.LIVEDESK_CLIENT_REQUIRE_FAST = requiresFastTransport(prepared) ? '1' : '0';
|
|
3898
4120
|
env.LIVEDESK_UDP_ENABLED = String(env.LIVEDESK_UDP_ENABLED || '1');
|
|
3899
4121
|
env.LIVEDESK_CLIENT_UPDATE_STATE_PATH = CLIENT_UPDATE_STATE_PATH;
|
|
3900
4122
|
env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 = Buffer
|
|
@@ -4007,23 +4229,35 @@ function prepareFastExecutable(executable) {
|
|
|
4007
4229
|
}
|
|
4008
4230
|
}
|
|
4009
4231
|
|
|
4010
|
-
function buildFastArgs(args, fakeThumbnail) {
|
|
4011
|
-
const forwarded = [];
|
|
4012
|
-
for (
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4232
|
+
export function buildFastArgs(args, fakeThumbnail, transport = 'auto', relay = '') {
|
|
4233
|
+
const forwarded = [];
|
|
4234
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
4235
|
+
const arg = args[index];
|
|
4236
|
+
if (arg === '--transport' || arg === '--relay') {
|
|
4237
|
+
index += 1;
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
if (arg === '--fake-thumbnail') {
|
|
4241
|
+
forwarded.push('--fake-frame');
|
|
4242
|
+
continue;
|
|
4016
4243
|
}
|
|
4017
4244
|
if (arg === '--tasks' || arg === '--no-tasks' || arg === '--no-ai') {
|
|
4018
4245
|
continue;
|
|
4019
4246
|
}
|
|
4020
4247
|
forwarded.push(arg);
|
|
4021
4248
|
}
|
|
4022
|
-
if (fakeThumbnail && !forwarded.includes('--fake-frame')) {
|
|
4023
|
-
forwarded.push('--fake-frame');
|
|
4024
|
-
}
|
|
4025
|
-
|
|
4026
|
-
|
|
4249
|
+
if (fakeThumbnail && !forwarded.includes('--fake-frame')) {
|
|
4250
|
+
forwarded.push('--fake-frame');
|
|
4251
|
+
}
|
|
4252
|
+
let launchArgs = upsertForwardedOption(
|
|
4253
|
+
forwarded,
|
|
4254
|
+
'--transport',
|
|
4255
|
+
normalizeClientTransport(transport));
|
|
4256
|
+
if (transportAllowsRelay(normalizeClientTransport(transport))) {
|
|
4257
|
+
launchArgs = upsertForwardedOption(launchArgs, '--relay', relay);
|
|
4258
|
+
}
|
|
4259
|
+
return launchArgs;
|
|
4260
|
+
}
|
|
4027
4261
|
|
|
4028
4262
|
function redactAgentArgs(args = []) {
|
|
4029
4263
|
const redacted = [];
|
|
@@ -4137,7 +4371,10 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
|
4137
4371
|
});
|
|
4138
4372
|
child.livedeskOwnsProcessGroup = ownsProcessGroup;
|
|
4139
4373
|
child.livedeskWindowsTreeTracker = createWindowsProcessTreeTracker(child, {
|
|
4140
|
-
spawnedAtMs: agentSpawnedAtMs
|
|
4374
|
+
spawnedAtMs: agentSpawnedAtMs,
|
|
4375
|
+
publishTrackedRecords: WINDOWS_PROCESS_MANIFEST_OWNER
|
|
4376
|
+
? publishWindowsOwnedAgentRecords
|
|
4377
|
+
: null
|
|
4141
4378
|
});
|
|
4142
4379
|
activeAgentProcess = child;
|
|
4143
4380
|
if (typeof onStart === 'function') {
|
|
@@ -4169,7 +4406,7 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
|
4169
4406
|
try {
|
|
4170
4407
|
if (process.platform === 'win32') {
|
|
4171
4408
|
if (!child.livedeskTreeStopPromise) {
|
|
4172
|
-
child.livedeskTreeStopPromise =
|
|
4409
|
+
child.livedeskTreeStopPromise = drainOwnedWindowsAgentTreeUntilStopped(child);
|
|
4173
4410
|
}
|
|
4174
4411
|
const windowsTreeStop = await child.livedeskTreeStopPromise;
|
|
4175
4412
|
if (windowsTreeStop?.ok === false) {
|
|
@@ -4219,12 +4456,20 @@ function shouldUseFast(parsed) {
|
|
|
4219
4456
|
return !parsed.nodeOnlyFeature;
|
|
4220
4457
|
}
|
|
4221
4458
|
|
|
4222
|
-
function shouldTryFast(parsed, runtime) {
|
|
4459
|
+
function shouldTryFast(parsed, runtime) {
|
|
4223
4460
|
if (parsed.engine === 'fast') {
|
|
4224
4461
|
return true;
|
|
4225
4462
|
}
|
|
4226
4463
|
return shouldUseFast(parsed) && !!runtime;
|
|
4227
|
-
}
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
export function requiresFastTransport(prepared = {}) {
|
|
4467
|
+
return prepared.requireFast === true
|
|
4468
|
+
|| prepared.connectionTransport === 'relay-fallback'
|
|
4469
|
+
|| prepared.transport === 'udp-p2p'
|
|
4470
|
+
|| prepared.transport === 'ws'
|
|
4471
|
+
|| prepared.relayExplicit === true;
|
|
4472
|
+
}
|
|
4228
4473
|
|
|
4229
4474
|
export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
4230
4475
|
startNetworkChangeMonitor();
|
|
@@ -4278,9 +4523,16 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4278
4523
|
await spawnUnifiedRoleRestart(roleRestartBeforeAgent.role);
|
|
4279
4524
|
connectionPage?.close?.();
|
|
4280
4525
|
process.exit(0);
|
|
4281
|
-
}
|
|
4526
|
+
}
|
|
4282
4527
|
const fastRuntime = getFastRuntime();
|
|
4283
|
-
const
|
|
4528
|
+
const fastRequired = requiresFastTransport(prepared);
|
|
4529
|
+
if (fastRequired && prepared.engine === 'node') {
|
|
4530
|
+
throw new Error(
|
|
4531
|
+
'The legacy Node engine cannot use WebSocket, UDP P2P, or encrypted relay transport. '
|
|
4532
|
+
+ 'Use --engine fast, or use --transport tcp while the Hub is directly reachable.'
|
|
4533
|
+
);
|
|
4534
|
+
}
|
|
4535
|
+
const useFast = fastRequired || shouldTryFast(prepared, fastRuntime);
|
|
4284
4536
|
const agentLaunchStartedAt = Date.now();
|
|
4285
4537
|
const updateAgentDashboard = (patch = {}) => {
|
|
4286
4538
|
prepared.connectionPage?.update({
|
|
@@ -4294,11 +4546,15 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4294
4546
|
? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
|
|
4295
4547
|
: 'LiveDesk client agent is launching.'
|
|
4296
4548
|
});
|
|
4297
|
-
};
|
|
4298
|
-
let result;
|
|
4299
|
-
if (useFast) {
|
|
4300
|
-
const fastArgs = buildFastArgs(
|
|
4301
|
-
|
|
4549
|
+
};
|
|
4550
|
+
let result;
|
|
4551
|
+
if (useFast) {
|
|
4552
|
+
const fastArgs = buildFastArgs(
|
|
4553
|
+
prepared.forwarded,
|
|
4554
|
+
prepared.fakeThumbnail,
|
|
4555
|
+
prepared.transport,
|
|
4556
|
+
prepared.relay);
|
|
4557
|
+
const fastLaunch = resolveFastLaunch(fastRuntime);
|
|
4302
4558
|
if (fastLaunch.ok) {
|
|
4303
4559
|
const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
|
|
4304
4560
|
updateAgentDashboard({
|
|
@@ -4319,10 +4575,15 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4319
4575
|
state: 'running'
|
|
4320
4576
|
});
|
|
4321
4577
|
});
|
|
4322
|
-
} else if (prepared.engine === 'fast') {
|
|
4323
|
-
console.error(
|
|
4324
|
-
|
|
4325
|
-
|
|
4578
|
+
} else if (prepared.engine === 'fast' || fastRequired) {
|
|
4579
|
+
console.error(
|
|
4580
|
+
`C# RemoteFast is unavailable: ${fastLaunch.reason}. `
|
|
4581
|
+
+ (fastRequired
|
|
4582
|
+
? 'The requested P2P, WebSocket, or encrypted relay transport requires the packaged RemoteFast runtime; the legacy Node agent was not started.'
|
|
4583
|
+
: 'Use --engine node to run the legacy Node agent.')
|
|
4584
|
+
);
|
|
4585
|
+
prepared.connectionPage?.close?.();
|
|
4586
|
+
process.exit(2);
|
|
4326
4587
|
} else {
|
|
4327
4588
|
console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
|
|
4328
4589
|
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|