livedesk 0.1.250 → 0.1.251

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 CHANGED
@@ -16,10 +16,12 @@ continues to listen on the LAN.
16
16
  On startup, the launcher finds stale processes whose command line identifies
17
17
  them as a LiveDesk Hub, even if the old Hub is still starting and has not
18
18
  finished binding its ports. It also checks the Hub ports (`5179` and `5197`),
19
- waits for the old process and both ports to be released, and then starts the
20
- new Hub. An unrelated process is preserved and reported instead of being
21
- terminated; use `--port`, `--remote-port`, or stop that process manually. Pass
22
- `--no-clean` to disable the startup cleanup explicitly.
19
+ waits for stale LiveDesk processes to exit, and then starts the new Hub. An
20
+ unrelated process is preserved and reported instead of being terminated. If
21
+ the client port remains occupied, the Hub automatically retries on a nearby
22
+ port and publishes that actual endpoint to clients. The HTTP manager port
23
+ remains strict so a web UI collision is reported clearly. Pass `--no-clean` to
24
+ disable the startup cleanup explicitly.
23
25
 
24
26
  ## Client
25
27
 
package/bin/livedesk.js CHANGED
@@ -37,7 +37,8 @@ Hub options:
37
37
  --host <host> Hub HTTP host. Default: 127.0.0.1
38
38
  --port <port> Hub HTTP port. Default: 5179
39
39
  --remote-port <port>
40
- Client connection port. Default: 5197
40
+ Preferred client connection port. Default: 5197; if busy,
41
+ LiveDesk retries on a nearby port automatically.
41
42
  --no-clean Do not stop existing processes on the Hub ports.
42
43
 
43
44
  Common:
@@ -212,7 +213,7 @@ function runQuiet(command, args) {
212
213
  });
213
214
  }
214
215
 
215
- async function stopProcessesOnPorts(ports) {
216
+ async function stopProcessesOnPorts(ports, { allowBusyPorts = false } = {}) {
216
217
  const uniquePorts = [...new Set(ports.map(port => normalizePort(port, 0)).filter(Boolean))];
217
218
  if (uniquePorts.length === 0) {
218
219
  return;
@@ -292,7 +293,11 @@ async function stopProcessesOnPorts(ports) {
292
293
  const blocked = records.filter(record => record?.PortAvailable !== true || (record?.Port === 0 && record?.ProcessGone !== true));
293
294
  if (blocked.length > 0) {
294
295
  const summary = blocked.map(record => `${record.Pid}:${record.ProcessName || 'unknown'}@${record.Port}[port-free=${record.PortAvailable === true},process-gone=${record.ProcessGone === true}]`).join(', ');
295
- throw new Error(`LiveDesk Hub startup cleanup could not release port/process owner(s): ${summary}. If this is an unrelated process, use --port/--remote-port or stop it manually.`);
296
+ const message = `LiveDesk Hub startup cleanup could not release port/process owner(s): ${summary}`;
297
+ if (!allowBusyPorts) {
298
+ throw new Error(`${message}. If this is an unrelated process, use --port or stop it manually.`);
299
+ }
300
+ console.warn(`${message}. The client endpoint will retry on another port automatically.`);
296
301
  }
297
302
  return;
298
303
  }
@@ -350,7 +355,8 @@ async function runManager(args) {
350
355
  || getStablePairToken();
351
356
 
352
357
  if (options.cleanPortsOnStart) {
353
- await stopProcessesOnPorts([httpPort, remotePort]);
358
+ await stopProcessesOnPorts([httpPort]);
359
+ await stopProcessesOnPorts([remotePort], { allowBusyPorts: true });
354
360
  }
355
361
 
356
362
  const internalHubEntry = resolve(packageRoot, 'hub', 'src', 'server.js');
@@ -4,6 +4,8 @@ import crypto from 'crypto';
4
4
 
5
5
  const DEFAULT_REMOTE_HUB_PORT = 5197;
6
6
  const DEFAULT_REMOTE_HUB_HOST = '0.0.0.0';
7
+ const DEFAULT_REMOTE_HUB_PORT_FALLBACK_ATTEMPTS = 32;
8
+ const REMOTE_HUB_AUTH_CALLBACK_PORT = 5198;
7
9
  const DEFAULT_HEARTBEAT_MS = 5000;
8
10
  const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
9
11
  const MAX_LINE_CHARS = 4 * 1024 * 1024;
@@ -1417,6 +1419,14 @@ export function createRemoteHub(options = {}) {
1417
1419
  && !isDisabledValue(env.REMOTE_HUB_DISABLED);
1418
1420
  const host = safeString(env.REMOTE_HUB_HOST || DEFAULT_REMOTE_HUB_HOST, 128);
1419
1421
  const requestedPort = normalizePort(env.REMOTE_HUB_PORT || DEFAULT_REMOTE_HUB_PORT);
1422
+ const remotePortFallbackEnabled = isEnabledValue(env.REMOTE_HUB_PORT_FALLBACK, true)
1423
+ && !isDisabledValue(env.REMOTE_HUB_PORT_FALLBACK_DISABLED);
1424
+ const remotePortFallbackStrict = isEnabledValue(env.REMOTE_HUB_PORT_FALLBACK_STRICT, false);
1425
+ const remotePortFallbackAttempts = clampNumber(
1426
+ env.REMOTE_HUB_PORT_FALLBACK_ATTEMPTS,
1427
+ 1,
1428
+ 128,
1429
+ DEFAULT_REMOTE_HUB_PORT_FALLBACK_ATTEMPTS);
1420
1430
  const heartbeatMs = clampNumber(env.REMOTE_HUB_HEARTBEAT_MS, 1000, 60000, DEFAULT_HEARTBEAT_MS);
1421
1431
  const taskTimeoutMs = clampNumber(
1422
1432
  options.taskTimeoutMs ?? env.REMOTE_HUB_TASK_TIMEOUT_MS,
@@ -4371,21 +4381,31 @@ export function createRemoteHub(options = {}) {
4371
4381
  });
4372
4382
  }
4373
4383
 
4374
- async function start() {
4375
- if (!enabled || started) {
4376
- return getStatus({ includeSecrets: false });
4384
+ function getNextFallbackPort(currentPort, attemptedPorts) {
4385
+ for (let offset = 1; offset <= 65535; offset += 1) {
4386
+ const candidate = (currentPort + offset) % 65536;
4387
+ if (candidate < 1 || candidate === REMOTE_HUB_AUTH_CALLBACK_PORT || attemptedPorts.has(candidate)) {
4388
+ continue;
4389
+ }
4390
+ return candidate;
4377
4391
  }
4392
+ return 0;
4393
+ }
4378
4394
 
4379
- await new Promise((resolve, reject) => {
4380
- server = net.createServer(handleSocket);
4381
- server.once('error', err => {
4395
+ function listenOnPort(port) {
4396
+ return new Promise((resolve, reject) => {
4397
+ const candidateServer = net.createServer(handleSocket);
4398
+ server = candidateServer;
4399
+ candidateServer.once('error', err => {
4382
4400
  lastError = err?.message || String(err);
4383
- server = null;
4401
+ if (server === candidateServer) {
4402
+ server = null;
4403
+ }
4384
4404
  reject(err);
4385
4405
  });
4386
- server.listen(requestedPort, host, () => {
4406
+ candidateServer.listen(port, host, () => {
4387
4407
  started = true;
4388
- boundPort = server.address()?.port || requestedPort;
4408
+ boundPort = candidateServer.address()?.port || port;
4389
4409
  lastError = '';
4390
4410
  logEvent('remote', `LiveDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
4391
4411
  if (host === '0.0.0.0' || host === '::') {
@@ -4395,6 +4415,48 @@ export function createRemoteHub(options = {}) {
4395
4415
  resolve();
4396
4416
  });
4397
4417
  });
4418
+ }
4419
+
4420
+ async function start() {
4421
+ if (!enabled || started) {
4422
+ return getStatus({ includeSecrets: false });
4423
+ }
4424
+
4425
+ const fallbackAllowed = remotePortFallbackEnabled
4426
+ && !remotePortFallbackStrict
4427
+ && requestedPort > 0;
4428
+ const attemptedPorts = new Set();
4429
+ let candidatePort = requestedPort;
4430
+ let lastBindError = null;
4431
+
4432
+ for (let attempt = 0; attempt < remotePortFallbackAttempts; attempt += 1) {
4433
+ attemptedPorts.add(candidatePort);
4434
+ try {
4435
+ await listenOnPort(candidatePort);
4436
+ if (candidatePort !== requestedPort) {
4437
+ logWarn(
4438
+ 'remote',
4439
+ `Client port ${requestedPort} is already in use; LiveDesk Hub is using ${candidatePort}.`);
4440
+ }
4441
+ lastBindError = null;
4442
+ break;
4443
+ } catch (error) {
4444
+ lastBindError = error;
4445
+ if (error?.code !== 'EADDRINUSE' || !fallbackAllowed) {
4446
+ throw error;
4447
+ }
4448
+
4449
+ const nextPort = getNextFallbackPort(candidatePort, attemptedPorts);
4450
+ if (!nextPort) {
4451
+ break;
4452
+ }
4453
+ candidatePort = nextPort;
4454
+ }
4455
+ }
4456
+
4457
+ if (lastBindError) {
4458
+ throw lastBindError;
4459
+ }
4398
4460
 
4399
4461
  return getStatus({ includeSecrets: false });
4400
4462
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.250",
3
+ "version": "0.1.251",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {