@livedesk/client 0.1.247 → 0.1.249

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.
@@ -19,7 +19,8 @@ import {
19
19
  signalAgentTree
20
20
  } from '../src/runtime/agent-process-lifecycle.js';
21
21
  import { writeWindowsOwnedProcessManifest } from '../src/runtime/windows-owned-process-manifest.js';
22
- import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
22
+ import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
23
+ import { resolveSpawnablePackagedPath } from '../src/runtime/packaged-executable-path.js';
23
24
  import {
24
25
  inspectLinuxVideoAcceleration,
25
26
  installLinuxVideoAcceleration
@@ -587,7 +588,7 @@ export function resolveHubClientPort(env = process.env) {
587
588
  return normalizePort(env.REMOTE_HUB_PORT || env.LIVEDESK_HUB_REMOTE_PORT) || DEFAULT_HUB_CLIENT_PORT;
588
589
  }
589
590
 
590
- export function preflightHubClientPort(port = resolveHubClientPort()) {
591
+ export function preflightHubClientPort(port = resolveHubClientPort()) {
591
592
  const normalizedPort = normalizePort(port);
592
593
  if (!normalizedPort) {
593
594
  return Promise.resolve({ ok: false, port: Number(port) || 0, code: 'invalid-hub-client-port' });
@@ -617,18 +618,46 @@ export function preflightHubClientPort(port = resolveHubClientPort()) {
617
618
  finish({ ok: true, code: 'ok' });
618
619
  });
619
620
  });
620
- });
621
- }
622
-
623
- function hubClientPortPreflightError(preflight) {
624
- const port = Number(preflight?.port || resolveHubClientPort());
625
- return {
626
- ok: false,
627
- code: String(preflight?.code || 'hub-client-port-unavailable'),
628
- port,
629
- error: preflight?.code === 'hub-client-port-in-use'
630
- ? `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.`
631
- : `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.`
621
+ });
622
+ }
623
+
624
+ export async function recoverHubClientPortConflict(preflight, recoverPortOwner) {
625
+ if (preflight?.ok
626
+ || preflight?.code !== 'hub-client-port-in-use'
627
+ || typeof recoverPortOwner !== 'function') {
628
+ return preflight;
629
+ }
630
+
631
+ try {
632
+ await recoverPortOwner(Number(preflight.port || resolveHubClientPort()));
633
+ } catch (error) {
634
+ return {
635
+ ...preflight,
636
+ recoveryAttempted: true,
637
+ recoveryError: error instanceof Error ? error.message : String(error)
638
+ };
639
+ }
640
+
641
+ const verified = await preflightHubClientPort(preflight.port);
642
+ return {
643
+ ...verified,
644
+ recoveryAttempted: true,
645
+ recovered: verified.ok === true
646
+ };
647
+ }
648
+
649
+ function hubClientPortPreflightError(preflight) {
650
+ const port = Number(preflight?.port || resolveHubClientPort());
651
+ const recoveryError = String(preflight?.recoveryError || '').trim();
652
+ return {
653
+ ok: false,
654
+ code: String(preflight?.code || 'hub-client-port-unavailable'),
655
+ port,
656
+ error: recoveryError
657
+ ? `${recoveryError} The current Hub was not changed.`
658
+ : preflight?.code === 'hub-client-port-in-use'
659
+ ? `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.`
660
+ : `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.`
632
661
  };
633
662
  }
634
663
 
@@ -3081,12 +3110,16 @@ async function startConnectionChoiceServer(supabase, options = {}) {
3081
3110
  res.end(JSON.stringify({ ok: false, error: 'client-can-only-transition-to-hub' }));
3082
3111
  return;
3083
3112
  }
3084
- const portPreflight = await preflightHubClientPort();
3085
- if (!portPreflight.ok) {
3086
- res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
3087
- res.end(JSON.stringify(hubClientPortPreflightError(portPreflight)));
3088
- return;
3089
- }
3113
+ const portPreflight = await preflightHubClientPort();
3114
+ const readyPortPreflight = await recoverHubClientPortConflict(
3115
+ portPreflight,
3116
+ options.recoverHubClientPort
3117
+ );
3118
+ if (!readyPortPreflight.ok) {
3119
+ res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
3120
+ res.end(JSON.stringify(hubClientPortPreflightError(readyPortPreflight)));
3121
+ return;
3122
+ }
3090
3123
  const session = await refreshSessionIfNeeded(supabase);
3091
3124
  const expectedRoleVersion = Number(dashboardState.roleVersion || 0);
3092
3125
  const { data, error } = await supabase.rpc('set_livedesk_device_role', {
@@ -3402,10 +3435,14 @@ async function chooseClientConnection(supabase, options = {}) {
3402
3435
  if (!activeSupabase) {
3403
3436
  return { ok: false, error: 'supabase-session-required' };
3404
3437
  }
3405
- const portPreflight = await preflightHubClientPort();
3406
- if (!portPreflight.ok) {
3407
- return hubClientPortPreflightError(portPreflight);
3408
- }
3438
+ const portPreflight = await preflightHubClientPort();
3439
+ const readyPortPreflight = await recoverHubClientPortConflict(
3440
+ portPreflight,
3441
+ options.recoverHubClientPort
3442
+ );
3443
+ if (!readyPortPreflight.ok) {
3444
+ return hubClientPortPreflightError(readyPortPreflight);
3445
+ }
3409
3446
  const session = await refreshSessionIfNeeded(activeSupabase);
3410
3447
  if (!session?.access_token) {
3411
3448
  return { ok: false, error: 'supabase-session-required' };
@@ -4428,14 +4465,14 @@ function getFastRuntime() {
4428
4465
  const arch = os.arch();
4429
4466
  const resolvePackagedRuntime = (packageName, rid, executableName) => {
4430
4467
  try {
4431
- const packagePath = require.resolve(`${packageName}/package.json`);
4432
- const fastRoot = join(dirname(packagePath), 'fast');
4433
- return {
4434
- rid,
4435
- packageName,
4436
- executable: join(fastRoot, executableName),
4437
- dll: join(fastRoot, 'livedesk-client-fast.dll')
4438
- };
4468
+ const packagePath = require.resolve(`${packageName}/package.json`);
4469
+ const fastRoot = join(dirname(packagePath), 'fast');
4470
+ return {
4471
+ rid,
4472
+ packageName,
4473
+ executable: resolveSpawnablePackagedPath(join(fastRoot, executableName)),
4474
+ dll: resolveSpawnablePackagedPath(join(fastRoot, 'livedesk-client-fast.dll'))
4475
+ };
4439
4476
  } catch {
4440
4477
  return {
4441
4478
  rid,
@@ -4570,13 +4607,15 @@ function clearFastPreflightCache() {
4570
4607
  }
4571
4608
  }
4572
4609
 
4573
- function resolveBundledFfmpegPaths() {
4574
- const paths = [];
4575
- const addPath = (candidate) => {
4576
- const ffmpegPath = String(candidate || '').trim();
4577
- if (ffmpegPath && existsSync(ffmpegPath) && !paths.includes(ffmpegPath)) {
4578
- paths.push(ffmpegPath);
4579
- }
4610
+ function resolveBundledFfmpegPaths() {
4611
+ const paths = [];
4612
+ const addPath = (candidate) => {
4613
+ // Electron can resolve ffmpeg-static through app.asar, but RemoteFast
4614
+ // is a native process and must receive the physical unpacked path.
4615
+ const ffmpegPath = resolveSpawnablePackagedPath(String(candidate || '').trim());
4616
+ if (ffmpegPath && existsSync(ffmpegPath) && !paths.includes(ffmpegPath)) {
4617
+ paths.push(ffmpegPath);
4618
+ }
4580
4619
  };
4581
4620
 
4582
4621
  const addFfmpegInstaller = () => {
@@ -5082,9 +5121,10 @@ export async function runClientRuntime(argv = process.argv.slice(2), runtimeOpti
5082
5121
  savedSession: null,
5083
5122
  loadSavedSession: false,
5084
5123
  savedPin: null,
5085
- allowRelayFallback: transportAllowsRelay(parsed.transport),
5086
- relayEndpoint: parsed.relay,
5087
- openBrowser: parsed.openBrowserOnStart,
5124
+ allowRelayFallback: transportAllowsRelay(parsed.transport),
5125
+ relayEndpoint: parsed.relay,
5126
+ recoverHubClientPort: runtimeOptions.recoverHubClientPort,
5127
+ openBrowser: parsed.openBrowserOnStart,
5088
5128
  onStarted: page => {
5089
5129
  connectionPage = page;
5090
5130
  resolveStarted(page);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.247",
3
+ "version": "0.1.249",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,10 +42,10 @@
42
42
  "ws": "^8.18.3"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@livedesk/fast-linux-x64": "0.1.444",
46
- "@livedesk/fast-osx-arm64": "0.1.444",
47
- "@livedesk/fast-osx-x64": "0.1.444",
48
- "@livedesk/fast-win-x64": "0.1.444"
45
+ "@livedesk/fast-linux-x64": "0.1.445",
46
+ "@livedesk/fast-osx-arm64": "0.1.445",
47
+ "@livedesk/fast-osx-x64": "0.1.445",
48
+ "@livedesk/fast-win-x64": "0.1.445"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
@@ -0,0 +1,23 @@
1
+ import { existsSync } from 'node:fs';
2
+
3
+ const ASAR_PATH_SEGMENT = /([\\/])app\.asar([\\/])/i;
4
+
5
+ /**
6
+ * Electron's Node loader can read JavaScript through the virtual app.asar
7
+ * path, but the operating system cannot spawn an executable from that virtual
8
+ * archive. electron-builder places executable payloads in the matching
9
+ * app.asar.unpacked tree, so only spawn targets cross that boundary.
10
+ */
11
+ export function resolveSpawnablePackagedPath(value, pathExists = existsSync) {
12
+ const candidate = String(value || '');
13
+ if (!candidate || !ASAR_PATH_SEGMENT.test(candidate)) {
14
+ return candidate;
15
+ }
16
+
17
+ const unpacked = candidate.replace(ASAR_PATH_SEGMENT, '$1app.asar.unpacked$2');
18
+ try {
19
+ return pathExists(unpacked) ? unpacked : candidate;
20
+ } catch {
21
+ return candidate;
22
+ }
23
+ }