@livedesk/client 0.1.173 → 0.1.175
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/livedesk-client.js +129 -45
- package/package.json +5 -5
- package/src/runtime/client-runtime-server.js +1 -0
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ Frame pipeline roadmap:
|
|
|
89
89
|
|
|
90
90
|
- Mode 1: `mode1-jpeg` - current test path using screen capture, resize, and JPEG binary frames.
|
|
91
91
|
- Mode 2: `mode2-lzo` - cross-platform wall path using independent RGB565LE frames capped at 320x180 and compressed as LZO1X blocks. The wall defaults to 8 fps. macOS and Linux keep one persistent local capture helper per client process. Legacy `mode2-lz4` settings migrate to this mode.
|
|
92
|
-
- Mode 3: `mode3-h264-hw` - OS-specific
|
|
92
|
+
- Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting LiveDesk through the published npm package. macOS emits one startup key frame and then an approximately one-second GOP. The launcher uses bundled ffmpeg for fallback paths unless `LIVEDESK_FFMPEG` points to a custom binary. On macOS, set `LIVEDESK_FFMPEG_AVFOUNDATION_INPUT` only when the ScreenCaptureKit helper is unavailable and the AVFoundation fallback input is not `1:none`.
|
|
93
93
|
|
|
94
94
|
Legacy mode names such as `remote-fast` and `remote-quality` are treated as
|
|
95
95
|
Mode 1 aliases.
|
package/bin/livedesk-client.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createClientRuntimeServer } from '../src/runtime/client-runtime-server.
|
|
|
14
14
|
import { installAgentTerminationHandlers } from '../src/runtime/agent-process-lifecycle.js';
|
|
15
15
|
import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
|
|
16
16
|
import { normalizeRuntimeAuthSession } from '../../runtime-core/src/auth-session.js';
|
|
17
|
+
import { startRoleTransitionSupervisor } from '../../runtime-core/src/role-transition-supervisor.js';
|
|
17
18
|
|
|
18
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
@@ -21,6 +22,7 @@ const packageRoot = resolve(__dirname, '..');
|
|
|
21
22
|
const nodeAgentPath = join(__dirname, 'livedesk-client-node.js');
|
|
22
23
|
const FAST_PREFLIGHT_TIMEOUT_MS = 5000;
|
|
23
24
|
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
25
|
+
const DEFAULT_HUB_CLIENT_PORT = 5197;
|
|
24
26
|
const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
|
|
25
27
|
const DEFAULT_AUTH_CALLBACK_PORT = 5179;
|
|
26
28
|
const ENDPOINT_PROBE_TIMEOUT_MS = 900;
|
|
@@ -112,30 +114,53 @@ function writeLocalRoleCache(role, deviceId, roleVersion = 0, assignedHubId = ''
|
|
|
112
114
|
return true;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
117
|
+
export function buildUnifiedRoleRestartEnvironment(role, baseEnv = process.env, runtimePid = process.pid) {
|
|
118
|
+
const normalizedRole = String(role || '').trim().toLowerCase();
|
|
119
|
+
const waitPid = Number(runtimePid);
|
|
120
|
+
if (normalizedRole !== 'hub' && normalizedRole !== 'client') {
|
|
121
|
+
throw new Error(`Unsupported LiveDesk role restart: ${role}`);
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isInteger(waitPid) || waitPid <= 1) {
|
|
124
|
+
throw new Error(`Invalid LiveDesk runtime PID for role restart: ${runtimePid}`);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...baseEnv,
|
|
128
|
+
LIVEDESK_ROLE_TRANSITION: '1',
|
|
129
|
+
// The replacement must wait only for this Client runtime. Waiting for
|
|
130
|
+
// npm, npx, PowerShell, or another terminal parent can deadlock the
|
|
131
|
+
// takeover even after Supabase has selected this device as Hub.
|
|
132
|
+
LIVEDESK_RESTART_WAIT_PID: String(waitPid),
|
|
133
|
+
LIVEDESK_ROLE_TAKEOVER: normalizedRole === 'hub' ? '1' : '',
|
|
134
|
+
LIVEDESK_SKIP_BROWSER_OPEN: '1'
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function spawnUnifiedRoleRestart(role) {
|
|
139
|
+
if (isTruthy(process.env.LIVEDESK_DESKTOP_HOST)) {
|
|
140
|
+
console.log(`[LiveDesk] Desktop supervisor will restart the runtime as ${role}.`);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const unifiedEntry = String(process.env.LIVEDESK_UNIFIED_LAUNCHER_ENTRY || '').trim();
|
|
144
|
+
const restartEnvironment = buildUnifiedRoleRestartEnvironment(role);
|
|
145
|
+
if (!unifiedEntry || !existsSync(unifiedEntry)) {
|
|
146
|
+
throw new Error(`LiveDesk unified role launcher is unavailable: ${unifiedEntry || 'missing path'}`);
|
|
147
|
+
}
|
|
148
|
+
const handoff = await startRoleTransitionSupervisor({
|
|
149
|
+
role,
|
|
150
|
+
previousPid: process.pid,
|
|
151
|
+
launcherEntry: unifiedEntry,
|
|
152
|
+
stateDir: UNIFIED_CLIENT_STATE_DIR,
|
|
153
|
+
runtimePort: Number(process.env.LIVEDESK_CLIENT_RUNTIME_PORT || DEFAULT_AUTH_CALLBACK_PORT),
|
|
154
|
+
remotePort: resolveHubClientPort(),
|
|
155
|
+
env: restartEnvironment,
|
|
156
|
+
cwd: process.cwd()
|
|
157
|
+
});
|
|
158
|
+
console.log(
|
|
159
|
+
`[LiveDesk] Role transition supervisor claimed target=${role} `
|
|
160
|
+
+ `pid=${handoff.supervisorPid}. Diagnostics: ${handoff.logPath}`
|
|
161
|
+
);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
139
164
|
|
|
140
165
|
function printHelp() {
|
|
141
166
|
process.stdout.write(`
|
|
@@ -349,15 +374,64 @@ function parseLauncherArgs(argv) {
|
|
|
349
374
|
};
|
|
350
375
|
}
|
|
351
376
|
|
|
352
|
-
function normalizePort(value) {
|
|
377
|
+
function normalizePort(value) {
|
|
353
378
|
const port = Number(String(value || '').trim());
|
|
354
379
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
355
380
|
return 0;
|
|
356
381
|
}
|
|
357
382
|
return port;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function resolveHubClientPort(env = process.env) {
|
|
386
|
+
return normalizePort(env.REMOTE_HUB_PORT || env.LIVEDESK_HUB_REMOTE_PORT) || DEFAULT_HUB_CLIENT_PORT;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function preflightHubClientPort(port = resolveHubClientPort()) {
|
|
390
|
+
const normalizedPort = normalizePort(port);
|
|
391
|
+
if (!normalizedPort) {
|
|
392
|
+
return Promise.resolve({ ok: false, port: Number(port) || 0, code: 'invalid-hub-client-port' });
|
|
393
|
+
}
|
|
394
|
+
return new Promise(resolvePreflight => {
|
|
395
|
+
const probe = net.createServer();
|
|
396
|
+
let completed = false;
|
|
397
|
+
const finish = result => {
|
|
398
|
+
if (completed) return;
|
|
399
|
+
completed = true;
|
|
400
|
+
resolvePreflight({ port: normalizedPort, ...result });
|
|
401
|
+
};
|
|
402
|
+
probe.unref?.();
|
|
403
|
+
probe.once('error', error => {
|
|
404
|
+
finish({
|
|
405
|
+
ok: false,
|
|
406
|
+
code: error?.code === 'EADDRINUSE' ? 'hub-client-port-in-use' : 'hub-client-port-unavailable',
|
|
407
|
+
systemCode: String(error?.code || '')
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
probe.listen({ host: '0.0.0.0', port: normalizedPort, exclusive: true }, () => {
|
|
411
|
+
probe.close(error => {
|
|
412
|
+
if (error) {
|
|
413
|
+
finish({ ok: false, code: 'hub-client-port-unavailable', systemCode: String(error?.code || '') });
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
finish({ ok: true, code: 'ok' });
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function hubClientPortPreflightError(preflight) {
|
|
423
|
+
const port = Number(preflight?.port || resolveHubClientPort());
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
code: String(preflight?.code || 'hub-client-port-unavailable'),
|
|
427
|
+
port,
|
|
428
|
+
error: preflight?.code === 'hub-client-port-in-use'
|
|
429
|
+
? `LiveDesk cannot switch this computer to Hub because TCP ${port} is already in use. Change or stop the owning service, then try Switch to Hub again. The current Hub was not changed.`
|
|
430
|
+
: `LiveDesk cannot switch this computer to Hub because TCP ${port} is unavailable. Check the local port configuration, then try again. The current Hub was not changed.`
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function normalizeSlotNumber(value) {
|
|
361
435
|
const number = Number(String(value || '').trim());
|
|
362
436
|
if (!Number.isInteger(number) || number < 1 || number > 999) {
|
|
363
437
|
return '';
|
|
@@ -2628,12 +2702,18 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
2628
2702
|
payload = Object.fromEntries(params.entries());
|
|
2629
2703
|
}
|
|
2630
2704
|
const nextRole = String(payload.role || '').trim().toLowerCase();
|
|
2631
|
-
if (nextRole !== 'hub') {
|
|
2705
|
+
if (nextRole !== 'hub') {
|
|
2632
2706
|
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
|
|
2633
2707
|
res.end(JSON.stringify({ ok: false, error: 'client-can-only-transition-to-hub' }));
|
|
2634
|
-
return;
|
|
2635
|
-
}
|
|
2636
|
-
const
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const portPreflight = await preflightHubClientPort();
|
|
2711
|
+
if (!portPreflight.ok) {
|
|
2712
|
+
res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
|
|
2713
|
+
res.end(JSON.stringify(hubClientPortPreflightError(portPreflight)));
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
const session = await refreshSessionIfNeeded(supabase);
|
|
2637
2717
|
const expectedRoleVersion = Number(dashboardState.roleVersion || 0);
|
|
2638
2718
|
const { data, error } = await supabase.rpc('set_livedesk_device_role', {
|
|
2639
2719
|
p_device_id: deviceId,
|
|
@@ -2928,6 +3008,10 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
2928
3008
|
if (!supabase) {
|
|
2929
3009
|
return { ok: false, error: 'supabase-session-required' };
|
|
2930
3010
|
}
|
|
3011
|
+
const portPreflight = await preflightHubClientPort();
|
|
3012
|
+
if (!portPreflight.ok) {
|
|
3013
|
+
return hubClientPortPreflightError(portPreflight);
|
|
3014
|
+
}
|
|
2931
3015
|
const session = await refreshSessionIfNeeded(supabase);
|
|
2932
3016
|
if (!session?.access_token) {
|
|
2933
3017
|
return { ok: false, error: 'supabase-session-required' };
|
|
@@ -3688,7 +3772,7 @@ function buildFastEnvironment(prepared) {
|
|
|
3688
3772
|
env.LIVEDESK_FFMPEG_PATHS = merged.join(separator);
|
|
3689
3773
|
}
|
|
3690
3774
|
|
|
3691
|
-
if (process.platform === 'darwin' || env.LIVEDESK_DEBUG_FFMPEG === '1') {
|
|
3775
|
+
if (process.platform === 'darwin' || process.platform === 'linux' || env.LIVEDESK_DEBUG_FFMPEG === '1') {
|
|
3692
3776
|
const describe = path => path.includes('ffmpeg-static')
|
|
3693
3777
|
? 'ffmpeg-static'
|
|
3694
3778
|
: path.includes('@ffmpeg-installer')
|
|
@@ -3930,12 +4014,12 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
3930
4014
|
while (true) {
|
|
3931
4015
|
const prepared = await prepareLoginConnection(parsed, connectionPage);
|
|
3932
4016
|
connectionPage = prepared.connectionPage || connectionPage;
|
|
3933
|
-
const roleRestartBeforeAgent = connectionPage?.getRoleRestartRequest?.() || roleRestartRequest;
|
|
3934
|
-
if (roleRestartBeforeAgent?.role) {
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
process.exit(0);
|
|
4017
|
+
const roleRestartBeforeAgent = connectionPage?.getRoleRestartRequest?.() || roleRestartRequest;
|
|
4018
|
+
if (roleRestartBeforeAgent?.role) {
|
|
4019
|
+
if (isTruthy(process.env.LIVEDESK_DESKTOP_HOST)) process.exit(43);
|
|
4020
|
+
await spawnUnifiedRoleRestart(roleRestartBeforeAgent.role);
|
|
4021
|
+
connectionPage?.close?.();
|
|
4022
|
+
process.exit(0);
|
|
3939
4023
|
}
|
|
3940
4024
|
const fastRuntime = getFastRuntime();
|
|
3941
4025
|
const useFast = shouldTryFast(prepared, fastRuntime);
|
|
@@ -4024,12 +4108,12 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4024
4108
|
});
|
|
4025
4109
|
});
|
|
4026
4110
|
}
|
|
4027
|
-
const requestedRoleRestart = connectionPage?.getRoleRestartRequest?.();
|
|
4028
|
-
if (requestedRoleRestart?.role) {
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
process.exit(0);
|
|
4111
|
+
const requestedRoleRestart = connectionPage?.getRoleRestartRequest?.();
|
|
4112
|
+
if (requestedRoleRestart?.role) {
|
|
4113
|
+
if (isTruthy(process.env.LIVEDESK_DESKTOP_HOST)) process.exit(43);
|
|
4114
|
+
await spawnUnifiedRoleRestart(requestedRoleRestart.role);
|
|
4115
|
+
connectionPage?.close?.();
|
|
4116
|
+
process.exit(0);
|
|
4033
4117
|
}
|
|
4034
4118
|
if (result?.signal) {
|
|
4035
4119
|
connectionPage?.close?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.175",
|
|
4
4
|
"description": "LiveDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"ws": "^8.18.3"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
43
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
44
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
45
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
42
|
+
"@livedesk/fast-linux-x64": "0.1.381",
|
|
43
|
+
"@livedesk/fast-osx-arm64": "0.1.381",
|
|
44
|
+
"@livedesk/fast-osx-x64": "0.1.381",
|
|
45
|
+
"@livedesk/fast-win-x64": "0.1.381"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|
|
@@ -739,6 +739,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
739
739
|
osVersion: os.release(),
|
|
740
740
|
pid: process.pid,
|
|
741
741
|
cpuUsagePercent: Math.round(cpuUsagePercent * 10) / 10,
|
|
742
|
+
cpuLogicalCores: Math.max(1, os.cpus().length),
|
|
742
743
|
memoryTotalBytes: totalMemoryBytes,
|
|
743
744
|
memoryUsedBytes: Math.max(0, totalMemoryBytes - freeMemoryBytes),
|
|
744
745
|
uptimeSeconds: Math.max(0, Math.floor(os.uptime())),
|