@livedesk/client 0.1.57 → 0.1.58

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.
@@ -26,6 +26,7 @@ const SUPABASE_URL = process.env.LIVEDESK_SUPABASE_URL || 'https://otbyfkjxrkngv
26
26
  const SUPABASE_PUBLISHABLE_KEY = process.env.LIVEDESK_SUPABASE_PUBLISHABLE_KEY || 'sb_publishable_NpUs0RDJH2YnllsqTKO6TQ_1jTdSsNQ';
27
27
  const CLIENT_STATE_DIR = join(os.homedir(), '.livedesk-client');
28
28
  const CLIENT_AUTH_PATH = join(CLIENT_STATE_DIR, 'auth.json');
29
+ const CLIENT_PIN_PATH = join(CLIENT_STATE_DIR, 'pin.json');
29
30
  const CLIENT_AUTH_STORAGE_KEY = 'livedesk.client.supabase.auth';
30
31
 
31
32
  function printHelp() {
@@ -39,6 +40,7 @@ Usage:
39
40
  Default flow:
40
41
  Opens a LiveDesk connection page with Google sign-in or a 6-digit PIN.
41
42
  After auth, that page stays open as this computer's client dashboard.
43
+ Saved Google sign-in or saved PIN starts the client automatically.
42
44
  Omit the number for first-available placement, or pass 1-999 to pin a slot.
43
45
  If no LiveDesk Hub is active yet, the client keeps checking every 5 seconds.
44
46
 
@@ -941,6 +943,19 @@ function renderConnectionChoicePage({ error = '', pin = '', slot = '', startup =
941
943
  startupToggle.addEventListener('change', syncStartupFields);
942
944
  syncStartupFields();
943
945
  }
946
+ async function watchAutoConnection() {
947
+ try {
948
+ const response = await fetch('/api/state', { cache: 'no-store' });
949
+ if (!response.ok) return;
950
+ const state = await response.json();
951
+ if (state.completed) {
952
+ location.replace('/');
953
+ }
954
+ } catch {
955
+ }
956
+ }
957
+ setInterval(watchAutoConnection, 800);
958
+ watchAutoConnection();
944
959
  </script>
945
960
  </body>
946
961
  </html>`;
@@ -982,6 +997,7 @@ function renderConnectionDashboardPage(state = {}) {
982
997
  message,
983
998
  slot,
984
999
  startup: Boolean(state.startup),
1000
+ completed: true,
985
1001
  statusLabel
986
1002
  }).replaceAll('<', '\\u003c');
987
1003
 
@@ -1263,6 +1279,32 @@ function normalizePairingPin(value) {
1263
1279
  return /^\d{6}$/.test(pin) ? pin : '';
1264
1280
  }
1265
1281
 
1282
+ function readSavedPin() {
1283
+ try {
1284
+ const state = JSON.parse(readFileSync(CLIENT_PIN_PATH, 'utf8'));
1285
+ return normalizePairingPin(state?.pin);
1286
+ } catch {
1287
+ return '';
1288
+ }
1289
+ }
1290
+
1291
+ function writeSavedPin(pin) {
1292
+ const normalizedPin = normalizePairingPin(pin);
1293
+ if (!normalizedPin) {
1294
+ return false;
1295
+ }
1296
+ mkdirSync(dirname(CLIENT_PIN_PATH), { recursive: true });
1297
+ writeFileSync(CLIENT_PIN_PATH, JSON.stringify({
1298
+ pin: normalizedPin,
1299
+ updatedAt: new Date().toISOString()
1300
+ }, null, 2));
1301
+ return true;
1302
+ }
1303
+
1304
+ function clearSavedPin() {
1305
+ rmSync(CLIENT_PIN_PATH, { force: true });
1306
+ }
1307
+
1266
1308
  function readRequestBody(req, maxBytes = 4096) {
1267
1309
  return new Promise((resolve, reject) => {
1268
1310
  let body = '';
@@ -1314,11 +1356,42 @@ async function resolveManagerFromPin(supabase, pin) {
1314
1356
  };
1315
1357
  }
1316
1358
 
1359
+ async function waitForManagerFromSavedPin(supabase, pin, options = {}) {
1360
+ const intervalMs = Math.max(1000, Number(options.intervalMs || DISCOVERY_RETRY_MS));
1361
+ const intervalSeconds = Math.max(1, Math.round(intervalMs / 1000));
1362
+ const shouldStop = typeof options.shouldStop === 'function' ? options.shouldStop : () => false;
1363
+ let attempts = 0;
1364
+ let lastMessage = '';
1365
+ console.log(`Waiting for a LiveDesk Hub from saved PIN. This client will keep trying every ${intervalSeconds}s.`);
1366
+ while (true) {
1367
+ if (shouldStop()) {
1368
+ return null;
1369
+ }
1370
+ attempts += 1;
1371
+ try {
1372
+ return await resolveManagerFromPin(supabase, pin);
1373
+ } catch (err) {
1374
+ if (shouldStop()) {
1375
+ return null;
1376
+ }
1377
+ const message = formatDiscoveryError(err);
1378
+ if (message !== lastMessage || attempts === 1 || attempts % 6 === 0) {
1379
+ const suffix = attempts === 1 ? '' : ` attempt ${attempts}`;
1380
+ console.log(`Still waiting for LiveDesk Hub by saved PIN${suffix}: ${message}`);
1381
+ lastMessage = message;
1382
+ }
1383
+ await sleep(intervalMs);
1384
+ }
1385
+ }
1386
+ }
1387
+
1317
1388
  async function startConnectionChoiceServer(supabase, options = {}) {
1318
1389
  const host = String(process.env.LIVEDESK_CLIENT_AUTH_HOST || DEFAULT_AUTH_CALLBACK_HOST).trim() || DEFAULT_AUTH_CALLBACK_HOST;
1319
1390
  const port = normalizePort(options.authPort) || DEFAULT_AUTH_CALLBACK_PORT;
1320
1391
  const slot = normalizeSlotNumber(options.slot);
1321
1392
  const startupArgs = Array.isArray(options.startupArgs) ? options.startupArgs : [];
1393
+ const savedSession = options.savedSession?.access_token ? options.savedSession : null;
1394
+ const savedPin = normalizePairingPin(options.savedPin);
1322
1395
  let pendingStartup = isWindowsStartupRegistered();
1323
1396
  let listeningPort = port;
1324
1397
  let completed = false;
@@ -1379,6 +1452,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1379
1452
  message: dashboardState.message || completedMessage,
1380
1453
  slot: normalizeSlotNumber(slot) ? `Slot ${String(slot).padStart(3, '0')}` : 'First available',
1381
1454
  startup: Boolean(pendingStartup),
1455
+ completed,
1382
1456
  statusLabel: dashboardState.loggedOut
1383
1457
  ? 'Signed out for next restart'
1384
1458
  : manager
@@ -1389,6 +1463,38 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1389
1463
  };
1390
1464
  };
1391
1465
 
1466
+ const startSavedConnection = async () => {
1467
+ if (completed || dashboardState.loggedOut) {
1468
+ return;
1469
+ }
1470
+ if (savedSession?.access_token) {
1471
+ applyStartupPreference(pendingStartup, startupArgs);
1472
+ complete({ type: 'google', session: savedSession }, 'LiveDesk client dashboard', 'Saved Google sign-in found. The client is starting automatically.');
1473
+ return;
1474
+ }
1475
+ if (!savedPin) {
1476
+ return;
1477
+ }
1478
+ dashboardState.message = 'Saved LiveDesk PIN found. Waiting for the Hub to become reachable.';
1479
+ try {
1480
+ const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
1481
+ shouldStop: () => completed || dashboardState.loggedOut
1482
+ });
1483
+ if (!resolved) {
1484
+ return;
1485
+ }
1486
+ if (completed || dashboardState.loggedOut) {
1487
+ return;
1488
+ }
1489
+ applyStartupPreference(pendingStartup, startupArgs);
1490
+ complete({ type: 'pin', ...resolved }, 'LiveDesk client dashboard', 'Saved PIN accepted. The client is starting automatically.');
1491
+ } catch (err) {
1492
+ if (!completed) {
1493
+ dashboardState.message = `Saved PIN did not connect: ${formatDiscoveryError(err)}`;
1494
+ }
1495
+ }
1496
+ };
1497
+
1392
1498
  const handleError = (res, error, pin = '') => {
1393
1499
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1394
1500
  res.end(renderChoice({
@@ -1421,6 +1527,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1421
1527
  } catch {
1422
1528
  }
1423
1529
  rmSync(CLIENT_AUTH_PATH, { force: true });
1530
+ clearSavedPin();
1424
1531
  dashboardState.choice = null;
1425
1532
  dashboardState.loggedOut = true;
1426
1533
  dashboardState.manager = '';
@@ -1494,6 +1601,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1494
1601
  pin = normalizePairingPin(pin);
1495
1602
  try {
1496
1603
  const resolved = await resolveManagerFromPin(supabase, pin);
1604
+ writeSavedPin(pin);
1497
1605
  applyStartupPreference(pendingStartup, startupArgs);
1498
1606
  const choice = { type: 'pin', ...resolved };
1499
1607
  complete(choice, 'LiveDesk client dashboard', 'PIN accepted. The client is starting automatically.');
@@ -1582,6 +1690,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1582
1690
  });
1583
1691
  const address = server.address();
1584
1692
  listeningPort = typeof address === 'object' && address ? address.port : port;
1693
+ if (savedSession?.access_token || savedPin) {
1694
+ setImmediate(() => {
1695
+ void startSavedConnection();
1696
+ });
1697
+ }
1585
1698
  } catch (err) {
1586
1699
  if (err?.code === 'EADDRINUSE') {
1587
1700
  throw new Error(`LiveDesk connection page port ${port} is already in use. Close the app using it or run with --auth-port <port> and add that callback URL in Supabase Auth redirect URLs.`);
@@ -1744,20 +1857,28 @@ async function prepareLoginConnection(parsed) {
1744
1857
  const supabase = await createSupabaseClient();
1745
1858
  const startupArgs = buildStartupClientArgs(parsed);
1746
1859
  let savedSession = null;
1747
- if (parsed.startupRun) {
1748
- try {
1749
- savedSession = await refreshSessionIfNeeded(supabase);
1750
- } catch (err) {
1751
- console.warn(`LiveDesk saved sign-in could not be refreshed: ${err?.message || err}`);
1752
- }
1860
+ try {
1861
+ savedSession = await refreshSessionIfNeeded(supabase);
1862
+ } catch (err) {
1863
+ console.warn(`LiveDesk saved sign-in could not be refreshed: ${err?.message || err}`);
1753
1864
  }
1754
- const choice = savedSession?.access_token
1755
- ? { type: 'google', session: savedSession }
1756
- : await chooseClientConnection(supabase, {
1865
+ const savedPin = readSavedPin();
1866
+ let choice = null;
1867
+ if (parsed.startupRun && savedSession?.access_token) {
1868
+ choice = { type: 'google', session: savedSession };
1869
+ } else if (parsed.startupRun && savedPin) {
1870
+ const resolved = await waitForManagerFromSavedPin(supabase, savedPin);
1871
+ choice = { type: 'pin', ...resolved };
1872
+ }
1873
+ if (!choice) {
1874
+ choice = await chooseClientConnection(supabase, {
1757
1875
  authPort: parsed.authPort,
1758
1876
  slot: parsed.slot,
1759
- startupArgs
1877
+ startupArgs,
1878
+ savedSession,
1879
+ savedPin
1760
1880
  });
1881
+ }
1761
1882
  connectionPage = choice.connectionPage || null;
1762
1883
  if (choice.type === 'pin') {
1763
1884
  manager = choice.manager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {