@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.
- package/bin/livedesk-client.js +131 -10
- package/package.json +1 -1
package/bin/livedesk-client.js
CHANGED
|
@@ -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
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
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
|
|
1755
|
-
|
|
1756
|
-
|
|
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;
|