@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.
- package/bin/livedesk-client.js +77 -6
- package/package.json +1 -1
package/bin/livedesk-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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,
|