livedesk 0.1.253 → 0.1.255

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
@@ -13,15 +13,15 @@ This starts the LiveDesk Hub, opens the local screen wall, and accepts clients.
13
13
  The Hub UI/API listens on `127.0.0.1` by default while the client endpoint
14
14
  continues to listen on the LAN.
15
15
 
16
- On startup, the launcher finds stale processes whose command line identifies
17
- them as a LiveDesk Hub, even if the old Hub is still starting and has not
18
- finished binding its ports. It also checks the Hub ports (`5179` and `5197`),
19
- waits for stale LiveDesk processes to exit, and then starts the new Hub. The
20
- client endpoint remains fixed at TCP `5197` by default so firewall rules,
21
- router forwarding, and direct clients keep a stable contract. An unrelated
22
- process is preserved and reported instead of being terminated; stop it or use
23
- an explicit `--remote-port` value. Pass `--no-clean` to disable the startup
24
- cleanup explicitly.
16
+ On startup, the launcher checks the Hub ports (`5179` and `5197`), identifies
17
+ stale LiveDesk Hub processes from their Node command line, stops each PID only
18
+ once, waits for the ports to be released and bindable, and then starts the new
19
+ Hub. Unrelated processes are preserved and reported with their PID, name, and
20
+ command line. The client endpoint remains fixed at TCP `5197` by default so
21
+ firewall rules, router forwarding, and direct clients keep a stable contract.
22
+ Pass `--no-clean` to disable the startup cleanup explicitly. If a child Hub
23
+ still loses a startup race with `EADDRINUSE`, the launcher performs one cleanup
24
+ and startup retry, then exits without looping.
25
25
 
26
26
  ## Client
27
27
 
package/bin/livedesk.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createRequire } from 'node:module';
4
+ import net from 'node:net';
4
5
  import { dirname, join, resolve } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import { execFile, spawn } from 'node:child_process';
@@ -14,6 +15,9 @@ const packageRoot = resolve(__dirname, '..');
14
15
  const DEFAULT_MANAGER_HTTP_PORT = 5179;
15
16
  const DEFAULT_REMOTE_HUB_PORT = 5197;
16
17
  const DEFAULT_MANAGER_URL = `http://127.0.0.1:${DEFAULT_MANAGER_HTTP_PORT}`;
18
+ const PORT_CLEANUP_TIMEOUT_MS = 3000;
19
+ const PORT_CLEANUP_POLL_MS = 100;
20
+ const HUB_STARTUP_RETRY_LIMIT = 1;
17
21
  const MANAGER_STATE_DIR = join(os.homedir(), '.livedesk');
18
22
  const MANAGER_STATE_PATH = join(MANAGER_STATE_DIR, 'manager.json');
19
23
 
@@ -212,6 +216,134 @@ function runQuiet(command, args) {
212
216
  });
213
217
  }
214
218
 
219
+ function waitMilliseconds(milliseconds) {
220
+ return new Promise(resolve => setTimeout(resolve, milliseconds));
221
+ }
222
+
223
+ function canBindPort(port, host = '0.0.0.0') {
224
+ return new Promise(resolve => {
225
+ const server = net.createServer();
226
+ let settled = false;
227
+ const finish = value => {
228
+ if (settled) return;
229
+ settled = true;
230
+ resolve(value);
231
+ };
232
+ server.once('error', () => finish(false));
233
+ server.listen(port, host, () => {
234
+ server.close(() => finish(true));
235
+ });
236
+ });
237
+ }
238
+
239
+ function isKnownLiveDeskProcess(processName, commandLine) {
240
+ const normalizedName = String(processName || '').trim();
241
+ const normalizedCommandLine = String(commandLine || '').replaceAll('\\', '/');
242
+ return /^(?:node|node\.exe)$/i.test(normalizedName)
243
+ && /livedesk/i.test(normalizedCommandLine)
244
+ && /hub\/src\/server\.js/i.test(normalizedCommandLine)
245
+ && /(?:node_modules\/livedesk|packages\/livedesk)\/hub\/src\/server\.js/i.test(normalizedCommandLine);
246
+ }
247
+
248
+ function isPortConflictOutput(output) {
249
+ return /\bEADDRINUSE\b|address already in use|requires client endpoint TCP \d+/i.test(String(output || ''));
250
+ }
251
+
252
+ function formatPortOwner(record) {
253
+ return `${record?.Pid || 'unknown'}:${record?.ProcessName || 'unknown'} cmd=${String(record?.CommandLine || 'unavailable').replace(/\s+/g, ' ').slice(0, 240)}`;
254
+ }
255
+
256
+ function assertPortCleanupReady(finalRecords, portStates) {
257
+ const blockedPorts = portStates.filter(port => port?.Ready !== true);
258
+ if (blockedPorts.length === 0) {
259
+ return;
260
+ }
261
+
262
+ const summary = blockedPorts.map(port => {
263
+ const owners = finalRecords
264
+ .filter(record => Number(record?.Port) === Number(port?.Port))
265
+ .map(formatPortOwner);
266
+ return `TCP ${port.Port}[available=${port.PortAvailable === true},bindable=${port.PortBindable === true},owners=${owners.length ? owners.join('; ') : 'owner not visible'}`;
267
+ }).join('], ');
268
+ throw new Error(`LiveDesk Hub startup cleanup could not release the configured port(s): ${summary}]. Unrelated processes are preserved; stop the owning process manually or choose an explicit --port/--remote-port value.`);
269
+ }
270
+
271
+ async function collectUnixPortRecords(ports) {
272
+ const records = [];
273
+ for (const port of ports) {
274
+ const lsof = await runQuiet('lsof', ['-nP', '-ti', `tcp:${port}`, '-sTCP:LISTEN']);
275
+ if (!lsof.ok && !lsof.stdout) {
276
+ throw new Error(`Could not inspect listening port TCP ${port}: ${lsof.stderr || lsof.error?.message || 'lsof failed'}`);
277
+ }
278
+ const pids = [...new Set(lsof.stdout.split(/\r?\n/).map(value => Number(value.trim())).filter(Number.isInteger))];
279
+ for (const pid of pids) {
280
+ const [nameResult, commandResult] = await Promise.all([
281
+ runQuiet('ps', ['-p', String(pid), '-o', 'comm=']),
282
+ runQuiet('ps', ['-p', String(pid), '-o', 'args='])
283
+ ]);
284
+ const processName = nameResult.stdout.trim();
285
+ const commandLine = commandResult.stdout.trim();
286
+ records.push({
287
+ Port: port,
288
+ Pid: pid,
289
+ ProcessName: processName,
290
+ CommandLine: commandLine,
291
+ KnownLiveDesk: isKnownLiveDeskProcess(processName, commandLine),
292
+ Action: 'preserved'
293
+ });
294
+ }
295
+ }
296
+ const unique = new Map();
297
+ for (const record of records) {
298
+ unique.set(`${record.Port}:${record.Pid}`, record);
299
+ }
300
+ return [...unique.values()];
301
+ }
302
+
303
+ async function stopUnixProcessesOnPorts(ports) {
304
+ const records = await collectUnixPortRecords(ports);
305
+ const killedPids = new Set();
306
+ for (const record of records) {
307
+ if (!record.KnownLiveDesk || killedPids.has(record.Pid)) continue;
308
+ killedPids.add(record.Pid);
309
+ await runQuiet('kill', ['-KILL', String(record.Pid)]);
310
+ for (const matching of records.filter(candidate => candidate.Pid === record.Pid)) {
311
+ matching.Action = 'stopped';
312
+ }
313
+ }
314
+
315
+ const deadline = Date.now() + PORT_CLEANUP_TIMEOUT_MS;
316
+ let finalRecords = [];
317
+ let portStates = [];
318
+ do {
319
+ finalRecords = await collectUnixPortRecords(ports);
320
+ portStates = [];
321
+ for (const port of ports) {
322
+ const busy = finalRecords.some(record => Number(record.Port) === Number(port));
323
+ const bindable = busy ? false : await canBindPort(port);
324
+ portStates.push({
325
+ Port: port,
326
+ PortAvailable: !busy,
327
+ PortBindable: bindable,
328
+ Ready: !busy && bindable
329
+ });
330
+ }
331
+ if (portStates.every(state => state.Ready) || Date.now() >= deadline) break;
332
+ await waitMilliseconds(PORT_CLEANUP_POLL_MS);
333
+ } while (true);
334
+
335
+ const stopped = records.filter(record => record.Action === 'stopped');
336
+ if (stopped.length > 0) {
337
+ const stoppedOwners = new Map();
338
+ for (const record of stopped) {
339
+ if (!stoppedOwners.has(String(record.Pid))) stoppedOwners.set(String(record.Pid), record);
340
+ }
341
+ const summary = [...stoppedOwners.values()].map(record => `${record.Pid}:${record.ProcessName || 'node'}`).join(', ');
342
+ console.log(`Restarting LiveDesk Hub. Stopped stale LiveDesk owner(s) once: ${summary}`);
343
+ }
344
+ assertPortCleanupReady(finalRecords, portStates);
345
+ }
346
+
215
347
  async function stopProcessesOnPorts(ports) {
216
348
  const uniquePorts = [...new Set(ports.map(port => normalizePort(port, 0)).filter(Boolean))];
217
349
  if (uniquePorts.length === 0) {
@@ -223,19 +355,6 @@ async function stopProcessesOnPorts(ports) {
223
355
  '$ErrorActionPreference = "SilentlyContinue";',
224
356
  `$ports = @(${uniquePorts.join(',')});`,
225
357
  `$currentNodePid = ${process.pid};`,
226
- ' $liveDeskProcessPattern = "(?i)(?:livedesk|@livedesk)";',
227
- ' $hubServerProcessPattern = "(?i)(?:hub[\\\\/].*server\\.js|server\\.js)";',
228
- 'function Get-ProcessCommandChain($proc) {',
229
- ' $chain = @();',
230
- ' $current = $proc;',
231
- ' for ($depth = 0; $current -and $depth -lt 8; $depth++) {',
232
- ' $chain += [string]$current.CommandLine;',
233
- ' $parentId = [int]$current.ParentProcessId;',
234
- ' if ($parentId -le 0) { break }',
235
- ' $current = Get-CimInstance Win32_Process -Filter "ProcessId = $parentId" -ErrorAction SilentlyContinue;',
236
- ' }',
237
- ' return ($chain -join " ");',
238
- '}',
239
358
  'function Get-ListenConnections($port) {',
240
359
  ' $connections = @(Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue);',
241
360
  ' if ($connections.Count -gt 0) { return $connections }',
@@ -247,87 +366,93 @@ async function stopProcessesOnPorts(ports) {
247
366
  ' }',
248
367
  ' }',
249
368
  '}',
250
- '$records = foreach ($port in $ports) {',
251
- ' $connections = @(Get-ListenConnections $port | Where-Object { $_.OwningProcess -and $_.OwningProcess -ne $currentNodePid });',
369
+ 'function Get-PortRecords($port) {',
370
+ ' $connections = @(Get-ListenConnections $port | Where-Object { $_.OwningProcess -and $_.OwningProcess -ne $currentNodePid -and $_.OwningProcess -ne $PID });',
252
371
  ' foreach ($connection in $connections) {',
253
372
  ' $owner = [int]$connection.OwningProcess;',
254
373
  ' $proc = Get-CimInstance Win32_Process -Filter "ProcessId = $owner" -ErrorAction SilentlyContinue;',
255
374
  ' $processName = if ($proc) { [string]$proc.Name } else { "" };',
256
375
  ' $commandLine = if ($proc) { [string]$proc.CommandLine } else { "" };',
257
- ' $commandChain = if ($proc) { Get-ProcessCommandChain $proc } else { "" };',
258
- ' $knownLiveDesk = ($processName -match "(?i)^node(?:\\.exe)?$") -and ($commandChain -match $liveDeskProcessPattern) -and ($commandChain -match $hubServerProcessPattern);',
259
- ' [pscustomobject]@{ Port = [int]$port; Pid = $owner; ProcessName = $processName; CommandLine = $commandLine; KnownLiveDesk = [bool]$knownLiveDesk; Action = "preserved"; PortAvailable = $false; ProcessGone = $false }',
376
+ ' $isNodeProcess = $processName -match "(?i)^node(?:\\.exe)?$";',
377
+ ' $hasLiveDeskToken = $commandLine -match "(?i)livedesk";',
378
+ ' $hasHubServerPath = $commandLine -match "(?i)hub[\\\\/]src[\\\\/]server\\.js";',
379
+ ' $hasPackagedHubPath = $commandLine -match "(?i)(?:node_modules|packages)[\\\\/]livedesk[\\\\/]hub[\\\\/]src[\\\\/]server\\.js";',
380
+ ' $knownLiveDesk = [bool]($isNodeProcess -and $hasLiveDeskToken -and $hasHubServerPath -and $hasPackagedHubPath);',
381
+ ' [pscustomobject]@{ Port = [int]$port; Pid = $owner; ProcessName = $processName; CommandLine = $commandLine; KnownLiveDesk = $knownLiveDesk; Action = "preserved"; KillAttempted = $false }',
260
382
  ' }',
261
383
  '}',
262
- '$records = @($records | Sort-Object Port,Pid -Unique);',
263
- '$hubProcesses = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessId -and $_.ProcessId -ne $currentNodePid -and ([string]$_.Name) -ieq "node.exe" } | ForEach-Object {',
264
- ' $commandChain = Get-ProcessCommandChain $_;',
265
- ' if (($commandChain -match $liveDeskProcessPattern) -and ($commandChain -match $hubServerProcessPattern)) {',
266
- ' [pscustomobject]@{ ProcessId = [int]$_.ProcessId; ParentProcessId = [int]$_.ParentProcessId; Name = [string]$_.Name; CommandLine = [string]$_.CommandLine; CommandChain = $commandChain }',
267
- ' }',
268
- '});',
269
- 'foreach ($proc in $hubProcesses) {',
270
- ' if (-not (@($records | Where-Object { $_.Pid -eq [int]$proc.ProcessId }).Count)) {',
271
- ' $records += [pscustomobject]@{ Port = 0; Pid = [int]$proc.ProcessId; ProcessName = [string]$proc.Name; CommandLine = [string]$proc.CommandLine; KnownLiveDesk = $true; Action = "preserved"; PortAvailable = $false; ProcessGone = $false }',
384
+ 'function Test-PortBindable($port) {',
385
+ ' $listener = $null;',
386
+ ' try {',
387
+ ' $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, [int]$port);',
388
+ ' $listener.Start();',
389
+ ' return $true;',
390
+ ' } catch {',
391
+ ' return $false;',
392
+ ' } finally {',
393
+ ' if ($listener) { $listener.Stop(); }',
272
394
  ' }',
273
395
  '}',
274
- 'foreach ($record in @($records | Where-Object { $_.KnownLiveDesk })) {',
275
- ' Stop-Process -Id $record.Pid -Force -ErrorAction SilentlyContinue;',
276
- ' $record.Action = "stopped";',
396
+ '$records = @($ports | ForEach-Object { Get-PortRecords $_ } | Sort-Object Port,Pid -Unique);',
397
+ '$killableOwners = @($records | Where-Object { $_.KnownLiveDesk } | Group-Object Pid | ForEach-Object { @($_.Group)[0] });',
398
+ 'foreach ($owner in $killableOwners) {',
399
+ ' Stop-Process -Id $owner.Pid -Force -ErrorAction SilentlyContinue;',
400
+ ' foreach ($record in @($records | Where-Object { [int]$_.Pid -eq [int]$owner.Pid })) {',
401
+ ' $record.Action = "stopped";',
402
+ ' $record.KillAttempted = $true;',
403
+ ' }',
277
404
  '}',
278
- '$deadline = (Get-Date).ToUniversalTime().AddMilliseconds(3000);',
405
+ `$deadline = (Get-Date).ToUniversalTime().AddMilliseconds(${PORT_CLEANUP_TIMEOUT_MS});`,
406
+ '$finalRecords = @();',
407
+ '$portStates = @();',
279
408
  'do {',
280
- ' $busyPorts = @($ports | ForEach-Object { Get-ListenConnections $_ });',
281
- ' $busyHubPids = @($records | Where-Object { $_.KnownLiveDesk } | ForEach-Object { Get-Process -Id $_.Pid -ErrorAction SilentlyContinue });',
282
- ' if (($busyPorts.Count -eq 0 -and $busyHubPids.Count -eq 0) -or (Get-Date).ToUniversalTime() -ge $deadline) { break }',
283
- ' Start-Sleep -Milliseconds 100;',
409
+ ' $finalRecords = @($ports | ForEach-Object { Get-PortRecords $_ });',
410
+ ' $busyPorts = @($finalRecords | Select-Object -ExpandProperty Port -Unique);',
411
+ ' $portStates = @($ports | ForEach-Object {',
412
+ ' $port = [int]$_;',
413
+ ' $busy = $busyPorts -contains $port;',
414
+ ' $bindable = if ($busy) { $false } else { Test-PortBindable $port };',
415
+ ' [pscustomobject]@{ Port = $port; PortAvailable = [bool](-not $busy); PortBindable = [bool]$bindable; Ready = [bool](-not $busy -and $bindable) }',
416
+ ' });',
417
+ ' if ((@($portStates | Where-Object { -not $_.Ready }).Count -eq 0) -or (Get-Date).ToUniversalTime() -ge $deadline) { break }',
418
+ ` Start-Sleep -Milliseconds ${PORT_CLEANUP_POLL_MS};`,
284
419
  '} while ($true);',
285
- '$busyPortNumbers = @($busyPorts | Select-Object -ExpandProperty LocalPort -Unique);',
286
- 'foreach ($record in $records) {',
287
- ' $record.PortAvailable = $busyPortNumbers -notcontains $record.Port;',
288
- ' $record.ProcessGone = -not (Get-Process -Id $record.Pid -ErrorAction SilentlyContinue);',
289
- '}',
290
- '$records | ConvertTo-Json -Compress'
420
+ '[pscustomobject]@{ Records = @($records); FinalRecords = @($finalRecords); Ports = @($portStates) } | ConvertTo-Json -Compress -Depth 8'
291
421
  ].join(' ');
292
422
  const result = await runQuiet('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script]);
293
423
  if (!result.ok && !result.stdout) {
294
424
  throw new Error(`Could not inspect existing LiveDesk Hub ports: ${result.stderr || result.error?.message || 'PowerShell failed'}`);
295
425
  }
296
- let records = [];
426
+ let payload = { Records: [], FinalRecords: [], Ports: [] };
297
427
  if (result.stdout) {
298
428
  try {
299
429
  const parsed = JSON.parse(result.stdout);
300
- records = Array.isArray(parsed) ? parsed : [parsed];
430
+ payload = Array.isArray(parsed)
431
+ ? { Records: parsed, FinalRecords: parsed, Ports: [] }
432
+ : parsed;
301
433
  } catch {
302
434
  throw new Error(`Could not parse existing LiveDesk Hub port state: ${result.stdout}`);
303
435
  }
304
436
  }
437
+ const records = Array.isArray(payload?.Records) ? payload.Records : [];
438
+ const finalRecords = Array.isArray(payload?.FinalRecords) ? payload.FinalRecords : [];
439
+ const portStates = Array.isArray(payload?.Ports) ? payload.Ports : [];
305
440
  const stopped = records.filter(record => record?.Action === 'stopped');
306
441
  if (stopped.length > 0) {
307
- const summary = stopped.map(record => `${record.Pid}:${record.ProcessName || 'node'}${record.Port ? `@${record.Port}` : ''}`).join(', ');
308
- console.log(`Restarting LiveDesk Hub. Stopped stale LiveDesk owner(s): ${summary}`);
309
- }
310
- const blocked = records.filter(record => record?.PortAvailable !== true || (record?.Port === 0 && record?.ProcessGone !== true));
311
- if (blocked.length > 0) {
312
- const summary = blocked.map(record => `${record.Pid}:${record.ProcessName || 'unknown'}@${record.Port}[port-free=${record.PortAvailable === true},process-gone=${record.ProcessGone === true},cmd=${String(record.CommandLine || 'unavailable').replace(/\s+/g, ' ').slice(0, 240)}]`).join(', ');
313
- throw new Error(`LiveDesk Hub startup cleanup could not release port/process owner(s): ${summary}. LiveDesk keeps the configured port fixed; stop that process or choose an explicit --port/--remote-port value.`);
442
+ const stoppedOwners = new Map();
443
+ for (const record of stopped) {
444
+ if (!stoppedOwners.has(String(record.Pid))) {
445
+ stoppedOwners.set(String(record.Pid), record);
446
+ }
447
+ }
448
+ const summary = [...stoppedOwners.values()].map(record => `${record.Pid}:${record.ProcessName || 'node'}`).join(', ');
449
+ console.log(`Restarting LiveDesk Hub. Stopped stale LiveDesk owner(s) once: ${summary}`);
314
450
  }
451
+ assertPortCleanupReady(finalRecords, portStates);
315
452
  return;
316
453
  }
317
454
 
318
- const script = uniquePorts.map(port => [
319
- `pids="$(lsof -ti tcp:${port} -sTCP:LISTEN 2>/dev/null || true)"`,
320
- 'for pid in $pids; do',
321
- ' if [ "$pid" != "$$" ]; then',
322
- ' kill "$pid" 2>/dev/null || true',
323
- ' echo "$pid"',
324
- ' fi',
325
- 'done'
326
- ].join('; ')).join('; ');
327
- const result = await runQuiet('sh', ['-c', script]);
328
- if (result.stdout) {
329
- console.log(`Restarting LiveDesk Hub. Cleared port owner(s): ${result.stdout.replace(/\r?\n/g, ', ')}`);
330
- }
455
+ await stopUnixProcessesOnPorts(uniquePorts);
331
456
  }
332
457
 
333
458
  async function waitForManager(url = DEFAULT_MANAGER_URL, timeoutMs = 10000) {
@@ -386,23 +511,54 @@ async function runManager(args) {
386
511
  LIVEDESK_WEB_DIST: existsSync(resolve(packagedWebDist, 'index.html')) ? packagedWebDist : (process.env.LIVEDESK_WEB_DIST || '')
387
512
  };
388
513
 
389
- const child = spawn(process.execPath, [hubEntry, ...options.forwarded], {
390
- env,
391
- stdio: 'inherit',
392
- windowsHide: false
393
- });
514
+ let startupRetryCount = 0;
515
+ const startHubProcess = () => {
516
+ const stderrChunks = [];
517
+ const child = spawn(process.execPath, [hubEntry, ...options.forwarded], {
518
+ env,
519
+ stdio: ['inherit', 'inherit', 'pipe'],
520
+ windowsHide: false
521
+ });
394
522
 
395
- child.once('error', error => {
396
- console.error(`Failed to start LiveDesk Hub: ${error.message}`);
397
- process.exitCode = 1;
398
- });
399
- child.once('exit', (code, signal) => {
400
- if (signal) {
401
- process.kill(process.pid, signal);
402
- return;
403
- }
404
- process.exit(code ?? 0);
405
- });
523
+ child.stderr.on('data', chunk => {
524
+ const text = chunk.toString();
525
+ stderrChunks.push(text);
526
+ while (stderrChunks.join('').length > 16_000) {
527
+ stderrChunks.shift();
528
+ }
529
+ process.stderr.write(chunk);
530
+ });
531
+
532
+ child.once('error', error => {
533
+ console.error(`Failed to start LiveDesk Hub: ${error.message}`);
534
+ process.exitCode = 1;
535
+ });
536
+ child.once('exit', (code, signal) => {
537
+ const startupOutput = stderrChunks.join('');
538
+ if (!signal
539
+ && code !== 0
540
+ && options.cleanPortsOnStart
541
+ && startupRetryCount < HUB_STARTUP_RETRY_LIMIT
542
+ && isPortConflictOutput(startupOutput)) {
543
+ startupRetryCount += 1;
544
+ console.warn('[LiveDesk Hub] Startup port conflict detected. Cleaning Hub ports and retrying once.');
545
+ void stopProcessesOnPorts([httpPort, remotePort])
546
+ .then(() => startHubProcess())
547
+ .catch(error => {
548
+ console.error(error?.message || error);
549
+ process.exitCode = 1;
550
+ });
551
+ return;
552
+ }
553
+ if (signal) {
554
+ process.kill(process.pid, signal);
555
+ return;
556
+ }
557
+ process.exit(code ?? 0);
558
+ });
559
+ };
560
+
561
+ startHubProcess();
406
562
 
407
563
  if (options.openBrowserOnStart) {
408
564
  void waitForManager(openUrl).then(ok => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.253",
3
+ "version": "0.1.255",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {