@livedesk/client 0.1.58 → 0.1.60

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.
@@ -406,6 +406,29 @@ function createFileStorage(filePath) {
406
406
  };
407
407
  }
408
408
 
409
+ function readSavedSessionFromFile() {
410
+ try {
411
+ const state = JSON.parse(readFileSync(CLIENT_AUTH_PATH, 'utf8'));
412
+ const raw = state?.[CLIENT_AUTH_STORAGE_KEY];
413
+ if (typeof raw !== 'string' || !raw.trim()) {
414
+ return null;
415
+ }
416
+ const session = JSON.parse(raw);
417
+ return session?.access_token ? session : null;
418
+ } catch {
419
+ return null;
420
+ }
421
+ }
422
+
423
+ function writeSavedSessionToFile(session) {
424
+ if (!session?.access_token || !session?.refresh_token) {
425
+ return false;
426
+ }
427
+ const storage = createFileStorage(CLIENT_AUTH_PATH);
428
+ storage.setItem(CLIENT_AUTH_STORAGE_KEY, JSON.stringify(session));
429
+ return true;
430
+ }
431
+
409
432
  async function createSupabaseClient() {
410
433
  const { createClient } = await import('@supabase/supabase-js');
411
434
  return createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
@@ -461,19 +484,22 @@ function formatDiscoveryError(error) {
461
484
 
462
485
  async function refreshSessionIfNeeded(supabase) {
463
486
  const { data: existing } = await supabase.auth.getSession();
464
- const session = existing?.session;
487
+ const session = existing?.session || readSavedSessionFromFile();
465
488
  if (!session?.access_token) {
466
489
  return null;
467
490
  }
468
491
  const expiresAt = Number(session.expires_at || 0);
469
492
  if (expiresAt <= 0 || expiresAt - Math.floor(Date.now() / 1000) > SESSION_REFRESH_SKEW_SECONDS) {
493
+ writeSavedSessionToFile(session);
470
494
  return session;
471
495
  }
472
496
  const { data, error } = await supabase.auth.refreshSession(session);
473
497
  if (error) {
474
498
  throw error;
475
499
  }
476
- return data?.session || session;
500
+ const refreshed = data?.session || session;
501
+ writeSavedSessionToFile(refreshed);
502
+ return refreshed;
477
503
  }
478
504
 
479
505
  function openBrowser(url) {
@@ -553,12 +579,16 @@ function renderOAuthCallbackPage({ title, message, tone = 'neutral' }) {
553
579
  </html>`;
554
580
  }
555
581
 
556
- function renderConnectionChoicePage({ error = '', pin = '', slot = '', startup = false, startupSupported = false } = {}) {
582
+ function renderConnectionChoicePage({ autoGoogle = false, error = '', pin = '', slot = '', startup = false, startupSupported = false } = {}) {
583
+ const shouldAutoGoogle = Boolean(autoGoogle && !error);
557
584
  const errorBlock = error
558
585
  ? `<div class="error">${escapeHtml(error)}</div>`
559
586
  : '';
560
587
  const normalizedSlot = normalizeSlotNumber(slot);
561
588
  const slotLabel = normalizedSlot ? `Slot ${String(normalizedSlot).padStart(3, '0')}` : 'First available';
589
+ const footerText = shouldAutoGoogle
590
+ ? 'Checking Google sign-in automatically. Enter a PIN to use the PIN path.'
591
+ : 'This tab can stay open until the connection is ready.';
562
592
  const startupBlock = startupSupported
563
593
  ? `<label class="startup-option">
564
594
  <input id="startup-toggle" type="checkbox" ${startup ? 'checked' : ''}>
@@ -900,7 +930,7 @@ function renderConnectionChoicePage({ error = '', pin = '', slot = '', startup =
900
930
  <input id="pin-startup" type="hidden" name="startup" value="${startup ? '1' : '0'}">
901
931
  <label class="field-label">
902
932
  Hub PIN
903
- <input class="pin-input" name="pin" value="${escapeHtml(pin)}" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6" placeholder="000000" autofocus>
933
+ <input class="pin-input" name="pin" value="${escapeHtml(pin)}" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6" placeholder="000000" ${shouldAutoGoogle ? '' : 'autofocus'}>
904
934
  </label>
905
935
  <button type="submit">Connect with PIN</button>
906
936
  </form>
@@ -910,7 +940,7 @@ function renderConnectionChoicePage({ error = '', pin = '', slot = '', startup =
910
940
  <strong>${escapeHtml(slotLabel)}</strong>
911
941
  </div>
912
942
  </section>
913
- <small>This tab can stay open until the connection is ready.</small>
943
+ <small>${escapeHtml(footerText)}</small>
914
944
  </div>
915
945
  </main>
916
946
  <aside aria-hidden="true">
@@ -943,6 +973,35 @@ function renderConnectionChoicePage({ error = '', pin = '', slot = '', startup =
943
973
  startupToggle.addEventListener('change', syncStartupFields);
944
974
  syncStartupFields();
945
975
  }
976
+ const autoGoogle = ${shouldAutoGoogle ? 'true' : 'false'};
977
+ let autoGoogleCanceled = false;
978
+ function cancelAutoGoogle() {
979
+ autoGoogleCanceled = true;
980
+ }
981
+ const pinInput = document.querySelector('.pin-input');
982
+ const pinForm = document.getElementById('pin-form');
983
+ if (pinInput) {
984
+ pinInput.addEventListener('focus', cancelAutoGoogle);
985
+ pinInput.addEventListener('input', cancelAutoGoogle);
986
+ pinInput.addEventListener('keydown', cancelAutoGoogle);
987
+ }
988
+ if (pinForm) {
989
+ pinForm.addEventListener('submit', cancelAutoGoogle);
990
+ }
991
+ if (startupToggle) {
992
+ startupToggle.addEventListener('change', cancelAutoGoogle);
993
+ }
994
+ if (autoGoogle) {
995
+ setTimeout(() => {
996
+ if (autoGoogleCanceled) return;
997
+ const googleForm = document.getElementById('google-form');
998
+ if (googleForm && typeof googleForm.requestSubmit === 'function') {
999
+ googleForm.requestSubmit();
1000
+ } else if (googleForm) {
1001
+ googleForm.submit();
1002
+ }
1003
+ }, 900);
1004
+ }
946
1005
  async function watchAutoConnection() {
947
1006
  try {
948
1007
  const response = await fetch('/api/state', { cache: 'no-store' });
@@ -1392,6 +1451,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1392
1451
  const startupArgs = Array.isArray(options.startupArgs) ? options.startupArgs : [];
1393
1452
  const savedSession = options.savedSession?.access_token ? options.savedSession : null;
1394
1453
  const savedPin = normalizePairingPin(options.savedPin);
1454
+ const autoGoogle = options.autoGoogle === true;
1395
1455
  let pendingStartup = isWindowsStartupRegistered();
1396
1456
  let listeningPort = port;
1397
1457
  let completed = false;
@@ -1432,6 +1492,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1432
1492
  }
1433
1493
  };
1434
1494
  const renderChoice = (props = {}) => renderConnectionChoicePage({
1495
+ autoGoogle,
1435
1496
  slot,
1436
1497
  startup: pendingStartup,
1437
1498
  startupSupported: isWindowsStartupSupported(),
@@ -1499,6 +1560,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1499
1560
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1500
1561
  res.end(renderChoice({
1501
1562
  error: error instanceof Error ? error.message : String(error),
1563
+ autoGoogle: false,
1502
1564
  pin
1503
1565
  }));
1504
1566
  };
@@ -1539,6 +1601,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1539
1601
  }
1540
1602
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1541
1603
  res.end(completed ? renderDashboard() : renderChoice({
1604
+ autoGoogle: false,
1542
1605
  error: 'Saved Google sign-in was cleared.'
1543
1606
  }));
1544
1607
  return;
@@ -1560,6 +1623,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1560
1623
  pendingStartup = normalizeStartupChoice(requestUrl.searchParams.get('startup'), pendingStartup);
1561
1624
  const { data: existing } = await supabase.auth.getSession();
1562
1625
  if (existing?.session?.access_token) {
1626
+ writeSavedSessionToFile(existing.session);
1563
1627
  applyStartupPreference(pendingStartup, startupArgs);
1564
1628
  const choice = { type: 'google', session: existing.session };
1565
1629
  complete(choice, 'LiveDesk client dashboard', 'Signed in. The client is starting automatically.');
@@ -1633,6 +1697,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1633
1697
  if (!code) {
1634
1698
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1635
1699
  res.end(renderChoice({
1700
+ autoGoogle: false,
1636
1701
  error: 'Google did not return an auth code. Try Google sign-in again.'
1637
1702
  }));
1638
1703
  return;
@@ -1653,6 +1718,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1653
1718
  server.close();
1654
1719
  return;
1655
1720
  }
1721
+ writeSavedSessionToFile(sessionData.session);
1656
1722
  applyStartupPreference(pendingStartup, startupArgs);
1657
1723
  const choice = { type: 'google', session: sessionData.session };
1658
1724
  complete(choice, 'LiveDesk client dashboard', 'Signed in. The client is starting automatically.');
@@ -1663,6 +1729,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1663
1729
 
1664
1730
  res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
1665
1731
  res.end(renderChoice({
1732
+ autoGoogle: false,
1666
1733
  error: 'Unknown LiveDesk connection route.'
1667
1734
  }));
1668
1735
  })().catch(err => {
@@ -1721,8 +1788,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1721
1788
  async function chooseClientConnection(supabase, options = {}) {
1722
1789
  const connectionPage = await startConnectionChoiceServer(supabase, {
1723
1790
  authPort: options.authPort,
1791
+ autoGoogle: options.autoGoogle,
1724
1792
  slot: options.slot,
1725
- startupArgs: options.startupArgs
1793
+ startupArgs: options.startupArgs,
1794
+ savedSession: options.savedSession,
1795
+ savedPin: options.savedPin
1726
1796
  });
1727
1797
  console.log('Opening LiveDesk connection page...');
1728
1798
  openBrowser(connectionPage.url);
@@ -1873,6 +1943,7 @@ async function prepareLoginConnection(parsed) {
1873
1943
  if (!choice) {
1874
1944
  choice = await chooseClientConnection(supabase, {
1875
1945
  authPort: parsed.authPort,
1946
+ autoGoogle: !savedSession?.access_token && !savedPin,
1876
1947
  slot: parsed.slot,
1877
1948
  startupArgs,
1878
1949
  savedSession,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {