@livedesk/client 0.1.56 → 0.1.57

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.
@@ -38,6 +38,7 @@ Usage:
38
38
 
39
39
  Default flow:
40
40
  Opens a LiveDesk connection page with Google sign-in or a 6-digit PIN.
41
+ After auth, that page stays open as this computer's client dashboard.
41
42
  Omit the number for first-available placement, or pass 1-999 to pin a slot.
42
43
  If no LiveDesk Hub is active yet, the client keeps checking every 5 seconds.
43
44
 
@@ -945,6 +946,318 @@ function renderConnectionChoicePage({ error = '', pin = '', slot = '', startup =
945
946
  </html>`;
946
947
  }
947
948
 
949
+ function getChoiceEmail(choice) {
950
+ return choice?.session?.user?.email || '';
951
+ }
952
+
953
+ function getDashboardAuthLabel(choice, loggedOut = false) {
954
+ if (loggedOut) {
955
+ return 'Signed out';
956
+ }
957
+ if (choice?.type === 'pin') {
958
+ return 'PIN approved';
959
+ }
960
+ const email = getChoiceEmail(choice);
961
+ return email ? `Google - ${email}` : 'Google signed in';
962
+ }
963
+
964
+ function renderConnectionDashboardPage(state = {}) {
965
+ const choice = state.choice || null;
966
+ const connectedAt = state.connectedAt || new Date().toISOString();
967
+ const authLabel = getDashboardAuthLabel(choice, state.loggedOut);
968
+ const manager = state.manager || (choice?.manager || '');
969
+ const slot = normalizeSlotNumber(state.slot) ? `Slot ${String(state.slot).padStart(3, '0')}` : 'First available';
970
+ const statusLabel = state.loggedOut
971
+ ? 'Signed out for next restart'
972
+ : manager
973
+ ? 'Client starting'
974
+ : 'Finding Hub';
975
+ const message = state.message || (manager
976
+ ? 'The LiveDesk client is running from the terminal. Keep this tab open as the local client dashboard.'
977
+ : 'Sign-in is complete. The client is looking for the active LiveDesk Hub and will start automatically.');
978
+ const stateJson = JSON.stringify({
979
+ authLabel,
980
+ connectedAt,
981
+ manager,
982
+ message,
983
+ slot,
984
+ startup: Boolean(state.startup),
985
+ statusLabel
986
+ }).replaceAll('<', '\\u003c');
987
+
988
+ return `<!doctype html>
989
+ <html lang="en">
990
+ <head>
991
+ <meta charset="utf-8">
992
+ <meta name="viewport" content="width=device-width, initial-scale=1">
993
+ <title>LiveDesk Client Dashboard</title>
994
+ <style>
995
+ :root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
996
+ * { box-sizing: border-box; }
997
+ html, body { width: 100%; min-height: 100%; margin: 0; }
998
+ body {
999
+ min-height: 100vh;
1000
+ display: grid;
1001
+ place-items: center;
1002
+ padding: 28px;
1003
+ background:
1004
+ radial-gradient(circle at 78% 10%, rgba(203, 213, 225, 0.46), transparent 24%),
1005
+ linear-gradient(180deg, #f8fafc, #eef2f7);
1006
+ color: #111827;
1007
+ }
1008
+ main {
1009
+ width: min(920px, 100%);
1010
+ display: grid;
1011
+ grid-template-columns: minmax(280px, 0.88fr) minmax(320px, 1.12fr);
1012
+ overflow: hidden;
1013
+ border: 1px solid rgba(148, 163, 184, 0.28);
1014
+ border-radius: 18px;
1015
+ background: rgba(255, 255, 255, 0.92);
1016
+ box-shadow: 0 36px 110px rgba(15, 23, 42, 0.14);
1017
+ }
1018
+ .hero {
1019
+ display: grid;
1020
+ align-content: space-between;
1021
+ gap: 34px;
1022
+ padding: 34px;
1023
+ border-right: 1px solid rgba(148, 163, 184, 0.22);
1024
+ background:
1025
+ linear-gradient(145deg, rgba(255, 255, 255, 0.9), rgba(248, 250, 252, 0.86)),
1026
+ #ffffff;
1027
+ }
1028
+ .brand {
1029
+ display: flex;
1030
+ align-items: center;
1031
+ gap: 13px;
1032
+ }
1033
+ .mark {
1034
+ width: 40px;
1035
+ height: 40px;
1036
+ display: grid;
1037
+ place-items: center;
1038
+ border-radius: 10px;
1039
+ background: linear-gradient(135deg, #2dd4bf, #2563eb);
1040
+ color: #ffffff;
1041
+ font-size: 13px;
1042
+ font-weight: 800;
1043
+ box-shadow: 0 14px 30px rgba(37, 99, 235, 0.18);
1044
+ }
1045
+ .brand-copy { display: grid; gap: 2px; line-height: 1; }
1046
+ .brand-copy strong { font-size: 15px; font-weight: 700; }
1047
+ .brand-copy span { color: #64748b; font-size: 12px; font-weight: 500; }
1048
+ .headline { display: grid; gap: 10px; }
1049
+ .eyebrow {
1050
+ color: #475569;
1051
+ font-size: 11px;
1052
+ font-weight: 800;
1053
+ letter-spacing: 0.08em;
1054
+ text-transform: uppercase;
1055
+ }
1056
+ h1 {
1057
+ margin: 0;
1058
+ font-size: 34px;
1059
+ line-height: 1.06;
1060
+ font-weight: 680;
1061
+ letter-spacing: 0;
1062
+ }
1063
+ p {
1064
+ margin: 0;
1065
+ color: #64748b;
1066
+ font-size: 14px;
1067
+ line-height: 1.62;
1068
+ font-weight: 450;
1069
+ }
1070
+ .panel {
1071
+ display: grid;
1072
+ gap: 16px;
1073
+ padding: 34px;
1074
+ }
1075
+ .status-card {
1076
+ display: grid;
1077
+ gap: 14px;
1078
+ padding: 18px;
1079
+ border: 1px solid rgba(148, 163, 184, 0.24);
1080
+ border-radius: 14px;
1081
+ background: #ffffff;
1082
+ box-shadow:
1083
+ 0 20px 50px rgba(15, 23, 42, 0.09),
1084
+ 0 1px 0 rgba(255, 255, 255, 0.9) inset;
1085
+ }
1086
+ .status-head {
1087
+ display: flex;
1088
+ align-items: center;
1089
+ justify-content: space-between;
1090
+ gap: 12px;
1091
+ }
1092
+ .status-pill {
1093
+ min-height: 28px;
1094
+ display: inline-flex;
1095
+ align-items: center;
1096
+ gap: 7px;
1097
+ padding: 0 10px;
1098
+ border: 1px solid rgba(20, 184, 166, 0.25);
1099
+ border-radius: 999px;
1100
+ background: rgba(20, 184, 166, 0.08);
1101
+ color: #0f766e;
1102
+ font-size: 12px;
1103
+ font-weight: 760;
1104
+ }
1105
+ .status-pill::before {
1106
+ content: "";
1107
+ width: 8px;
1108
+ height: 8px;
1109
+ border-radius: 999px;
1110
+ background: #14b8a6;
1111
+ box-shadow: 0 0 0 4px rgba(20, 184, 166, 0.12);
1112
+ }
1113
+ .status-card h2 {
1114
+ margin: 0;
1115
+ color: #111827;
1116
+ font-size: 20px;
1117
+ line-height: 1.18;
1118
+ }
1119
+ dl {
1120
+ display: grid;
1121
+ grid-template-columns: 118px minmax(0, 1fr);
1122
+ gap: 9px 14px;
1123
+ margin: 0;
1124
+ padding-top: 4px;
1125
+ }
1126
+ dt {
1127
+ color: #64748b;
1128
+ font-size: 12px;
1129
+ font-weight: 650;
1130
+ }
1131
+ dd {
1132
+ min-width: 0;
1133
+ margin: 0;
1134
+ overflow: hidden;
1135
+ color: #111827;
1136
+ font-size: 13px;
1137
+ font-weight: 680;
1138
+ text-overflow: ellipsis;
1139
+ white-space: nowrap;
1140
+ }
1141
+ .actions {
1142
+ display: grid;
1143
+ grid-template-columns: 1fr 1fr;
1144
+ gap: 10px;
1145
+ }
1146
+ button, a.button {
1147
+ min-height: 40px;
1148
+ display: inline-flex;
1149
+ align-items: center;
1150
+ justify-content: center;
1151
+ border: 1px solid #111827;
1152
+ border-radius: 9px;
1153
+ background: #111827;
1154
+ color: #ffffff;
1155
+ font-size: 13px;
1156
+ font-weight: 680;
1157
+ text-decoration: none;
1158
+ cursor: pointer;
1159
+ box-shadow: 0 12px 26px rgba(15, 23, 42, 0.13);
1160
+ }
1161
+ button.secondary, a.secondary {
1162
+ border-color: rgba(148, 163, 184, 0.36);
1163
+ background: #ffffff;
1164
+ color: #1f2937;
1165
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
1166
+ }
1167
+ .muted-card {
1168
+ padding: 14px 16px;
1169
+ border: 1px dashed rgba(148, 163, 184, 0.34);
1170
+ border-radius: 12px;
1171
+ background: rgba(248, 250, 252, 0.8);
1172
+ color: #64748b;
1173
+ font-size: 13px;
1174
+ line-height: 1.55;
1175
+ font-weight: 500;
1176
+ }
1177
+ @media (max-width: 760px) {
1178
+ body { padding: 14px; }
1179
+ main { grid-template-columns: 1fr; }
1180
+ .hero { border-right: 0; border-bottom: 1px solid rgba(148, 163, 184, 0.22); }
1181
+ .hero, .panel { padding: 24px; }
1182
+ h1 { font-size: 28px; }
1183
+ .actions { grid-template-columns: 1fr; }
1184
+ }
1185
+ </style>
1186
+ </head>
1187
+ <body>
1188
+ <main>
1189
+ <section class="hero">
1190
+ <div class="brand">
1191
+ <div class="mark">LD</div>
1192
+ <div class="brand-copy">
1193
+ <strong>LiveDesk</strong>
1194
+ <span>Client dashboard</span>
1195
+ </div>
1196
+ </div>
1197
+ <div class="headline">
1198
+ <span class="eyebrow">This computer</span>
1199
+ <h1>Client is ready</h1>
1200
+ <p id="message">${escapeHtml(message)}</p>
1201
+ </div>
1202
+ <p>Keep this page open if you want a local control surface for this workstation. Reboots can reconnect automatically when startup is enabled.</p>
1203
+ </section>
1204
+ <section class="panel">
1205
+ <article class="status-card">
1206
+ <div class="status-head">
1207
+ <h2>Connection</h2>
1208
+ <span id="status" class="status-pill">${escapeHtml(statusLabel)}</span>
1209
+ </div>
1210
+ <dl>
1211
+ <dt>Auth</dt><dd id="auth">${escapeHtml(authLabel)}</dd>
1212
+ <dt>Hub</dt><dd id="manager">${escapeHtml(manager || 'waiting')}</dd>
1213
+ <dt>Placement</dt><dd id="slot">${escapeHtml(slot)}</dd>
1214
+ <dt>Startup</dt><dd id="startup">${state.startup ? 'enabled' : 'manual'}</dd>
1215
+ <dt>Started</dt><dd id="connected-at">${escapeHtml(connectedAt)}</dd>
1216
+ </dl>
1217
+ </article>
1218
+ <div class="actions">
1219
+ <a class="button secondary" href="/">Refresh dashboard</a>
1220
+ <form method="post" action="/logout">
1221
+ <button class="secondary" type="submit">Logout</button>
1222
+ </form>
1223
+ </div>
1224
+ <div class="muted-card">The terminal process starts the screen stream automatically after auth. This dashboard stays available for logout and future workstation controls.</div>
1225
+ </section>
1226
+ </main>
1227
+ <script>
1228
+ const initialState = ${stateJson};
1229
+ function setText(id, value) {
1230
+ const node = document.getElementById(id);
1231
+ if (node) node.textContent = value || '';
1232
+ }
1233
+ function applyState(state) {
1234
+ setText('status', state.statusLabel || initialState.statusLabel);
1235
+ setText('auth', state.authLabel || initialState.authLabel);
1236
+ setText('manager', state.manager || 'waiting');
1237
+ setText('slot', state.slot || initialState.slot);
1238
+ setText('startup', state.startup ? 'enabled' : 'manual');
1239
+ setText('connected-at', state.connectedAt || initialState.connectedAt);
1240
+ setText('message', state.message || initialState.message);
1241
+ }
1242
+ applyState(initialState);
1243
+ if (location.pathname === '/callback') {
1244
+ history.replaceState(null, '', '/');
1245
+ }
1246
+ async function refreshState() {
1247
+ try {
1248
+ const response = await fetch('/api/state', { cache: 'no-store' });
1249
+ if (!response.ok) return;
1250
+ applyState(await response.json());
1251
+ } catch {
1252
+ }
1253
+ }
1254
+ setInterval(refreshState, 1000);
1255
+ refreshState();
1256
+ </script>
1257
+ </body>
1258
+ </html>`;
1259
+ }
1260
+
948
1261
  function normalizePairingPin(value) {
949
1262
  const pin = String(value || '').trim();
950
1263
  return /^\d{6}$/.test(pin) ? pin : '';
@@ -1010,7 +1323,17 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1010
1323
  let listeningPort = port;
1011
1324
  let completed = false;
1012
1325
  let completedTitle = 'LiveDesk connection complete';
1013
- let completedMessage = 'You can close this tab and return to the terminal.';
1326
+ let completedMessage = 'The LiveDesk client is starting automatically.';
1327
+ const dashboardState = {
1328
+ choice: null,
1329
+ connectedAt: '',
1330
+ endpointCandidates: [],
1331
+ loggedOut: false,
1332
+ manager: '',
1333
+ message: completedMessage,
1334
+ slot,
1335
+ startup: pendingStartup
1336
+ };
1014
1337
  let settleChoice;
1015
1338
  let rejectChoice;
1016
1339
  const waitForChoice = new Promise((resolve, reject) => {
@@ -1022,8 +1345,18 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1022
1345
  completed = true;
1023
1346
  completedTitle = title;
1024
1347
  completedMessage = message;
1025
- settleChoice(choice);
1026
- setImmediate(() => server.close());
1348
+ dashboardState.choice = choice;
1349
+ dashboardState.connectedAt = new Date().toISOString();
1350
+ dashboardState.endpointCandidates = Array.isArray(choice?.endpointCandidates) ? choice.endpointCandidates : [];
1351
+ dashboardState.loggedOut = false;
1352
+ dashboardState.manager = choice?.manager || '';
1353
+ dashboardState.message = message;
1354
+ dashboardState.startup = pendingStartup;
1355
+ if (settleChoice) {
1356
+ const resolveChoice = settleChoice;
1357
+ settleChoice = null;
1358
+ resolveChoice(choice);
1359
+ }
1027
1360
  };
1028
1361
  const renderChoice = (props = {}) => renderConnectionChoicePage({
1029
1362
  slot,
@@ -1031,6 +1364,30 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1031
1364
  startupSupported: isWindowsStartupSupported(),
1032
1365
  ...props
1033
1366
  });
1367
+ const renderDashboard = (props = {}) => renderConnectionDashboardPage({
1368
+ ...dashboardState,
1369
+ slot,
1370
+ startup: pendingStartup,
1371
+ ...props
1372
+ });
1373
+ const getDashboardApiState = () => {
1374
+ const manager = dashboardState.manager || dashboardState.choice?.manager || '';
1375
+ return {
1376
+ authLabel: getDashboardAuthLabel(dashboardState.choice, dashboardState.loggedOut),
1377
+ connectedAt: dashboardState.connectedAt || '',
1378
+ manager,
1379
+ message: dashboardState.message || completedMessage,
1380
+ slot: normalizeSlotNumber(slot) ? `Slot ${String(slot).padStart(3, '0')}` : 'First available',
1381
+ startup: Boolean(pendingStartup),
1382
+ statusLabel: dashboardState.loggedOut
1383
+ ? 'Signed out for next restart'
1384
+ : manager
1385
+ ? 'Client starting'
1386
+ : completed
1387
+ ? 'Finding Hub'
1388
+ : 'Waiting for auth'
1389
+ };
1390
+ };
1034
1391
 
1035
1392
  const handleError = (res, error, pin = '') => {
1036
1393
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -1048,15 +1405,44 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1048
1405
  res.end();
1049
1406
  return;
1050
1407
  }
1051
- if (completed) {
1408
+
1409
+ if (requestUrl.pathname === '/api/state') {
1410
+ res.writeHead(200, {
1411
+ 'Content-Type': 'application/json; charset=utf-8',
1412
+ 'Cache-Control': 'no-store'
1413
+ });
1414
+ res.end(JSON.stringify(getDashboardApiState()));
1415
+ return;
1416
+ }
1417
+
1418
+ if (requestUrl.pathname === '/logout') {
1419
+ try {
1420
+ await supabase.auth.signOut();
1421
+ } catch {
1422
+ }
1423
+ rmSync(CLIENT_AUTH_PATH, { force: true });
1424
+ dashboardState.choice = null;
1425
+ dashboardState.loggedOut = true;
1426
+ dashboardState.manager = '';
1427
+ dashboardState.message = 'Saved Google sign-in was cleared. Restart this client to sign in again.';
1428
+ if (req.method !== 'GET') {
1429
+ res.writeHead(303, { Location: completed ? '/' : '/connect' });
1430
+ res.end();
1431
+ return;
1432
+ }
1052
1433
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1053
- res.end(renderOAuthCallbackPage({
1054
- title: completedTitle,
1055
- message: completedMessage
1434
+ res.end(completed ? renderDashboard() : renderChoice({
1435
+ error: 'Saved Google sign-in was cleared.'
1056
1436
  }));
1057
1437
  return;
1058
1438
  }
1059
1439
 
1440
+ if (completed) {
1441
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1442
+ res.end(renderDashboard());
1443
+ return;
1444
+ }
1445
+
1060
1446
  if (requestUrl.pathname === '/' || requestUrl.pathname === '/connect') {
1061
1447
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1062
1448
  res.end(renderChoice());
@@ -1068,12 +1454,10 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1068
1454
  const { data: existing } = await supabase.auth.getSession();
1069
1455
  if (existing?.session?.access_token) {
1070
1456
  applyStartupPreference(pendingStartup, startupArgs);
1457
+ const choice = { type: 'google', session: existing.session };
1458
+ complete(choice, 'LiveDesk client dashboard', 'Signed in. The client is starting automatically.');
1071
1459
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1072
- res.end(renderOAuthCallbackPage({
1073
- title: 'LiveDesk sign-in ready',
1074
- message: 'You can close this tab and return to the terminal.'
1075
- }));
1076
- complete({ type: 'google', session: existing.session }, 'LiveDesk sign-in ready');
1460
+ res.end(renderDashboard());
1077
1461
  return;
1078
1462
  }
1079
1463
 
@@ -1111,12 +1495,10 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1111
1495
  try {
1112
1496
  const resolved = await resolveManagerFromPin(supabase, pin);
1113
1497
  applyStartupPreference(pendingStartup, startupArgs);
1498
+ const choice = { type: 'pin', ...resolved };
1499
+ complete(choice, 'LiveDesk client dashboard', 'PIN accepted. The client is starting automatically.');
1114
1500
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1115
- res.end(renderOAuthCallbackPage({
1116
- title: 'LiveDesk PIN accepted',
1117
- message: 'You can close this tab and return to the terminal.'
1118
- }));
1119
- complete({ type: 'pin', ...resolved }, 'LiveDesk PIN accepted');
1501
+ res.end(renderDashboard());
1120
1502
  } catch (err) {
1121
1503
  handleError(res, err, pin);
1122
1504
  }
@@ -1134,7 +1516,9 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1134
1516
  tone: 'error'
1135
1517
  }));
1136
1518
  completed = true;
1137
- rejectChoice(new Error(error));
1519
+ if (rejectChoice) {
1520
+ rejectChoice(new Error(error));
1521
+ }
1138
1522
  server.close();
1139
1523
  return;
1140
1524
  }
@@ -1155,17 +1539,17 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1155
1539
  tone: 'error'
1156
1540
  }));
1157
1541
  completed = true;
1158
- rejectChoice(message instanceof Error ? message : new Error(String(message)));
1542
+ if (rejectChoice) {
1543
+ rejectChoice(message instanceof Error ? message : new Error(String(message)));
1544
+ }
1159
1545
  server.close();
1160
1546
  return;
1161
1547
  }
1162
1548
  applyStartupPreference(pendingStartup, startupArgs);
1549
+ const choice = { type: 'google', session: sessionData.session };
1550
+ complete(choice, 'LiveDesk client dashboard', 'Signed in. The client is starting automatically.');
1163
1551
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1164
- res.end(renderOAuthCallbackPage({
1165
- title: 'LiveDesk sign-in complete',
1166
- message: 'You can close this tab and return to the terminal.'
1167
- }));
1168
- complete({ type: 'google', session: sessionData.session }, 'LiveDesk sign-in complete');
1552
+ res.end(renderDashboard());
1169
1553
  return;
1170
1554
  }
1171
1555
 
@@ -1184,7 +1568,9 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1184
1568
  }));
1185
1569
  if (!completed) {
1186
1570
  completed = true;
1187
- rejectChoice(err instanceof Error ? err : new Error(String(err)));
1571
+ if (rejectChoice) {
1572
+ rejectChoice(err instanceof Error ? err : new Error(String(err)));
1573
+ }
1188
1574
  server.close();
1189
1575
  }
1190
1576
  });
@@ -1206,7 +1592,16 @@ async function startConnectionChoiceServer(supabase, options = {}) {
1206
1592
  get url() {
1207
1593
  return `http://${host}:${listeningPort}/`;
1208
1594
  },
1209
- waitForChoice
1595
+ waitForChoice,
1596
+ close() {
1597
+ try {
1598
+ server.close();
1599
+ } catch {
1600
+ }
1601
+ },
1602
+ update(patch = {}) {
1603
+ Object.assign(dashboardState, patch);
1604
+ }
1210
1605
  };
1211
1606
  }
1212
1607
 
@@ -1218,7 +1613,8 @@ async function chooseClientConnection(supabase, options = {}) {
1218
1613
  });
1219
1614
  console.log('Opening LiveDesk connection page...');
1220
1615
  openBrowser(connectionPage.url);
1221
- return connectionPage.waitForChoice;
1616
+ const choice = await connectionPage.waitForChoice;
1617
+ return { ...choice, connectionPage };
1222
1618
  }
1223
1619
 
1224
1620
  function parseManagerEndpoint(value) {
@@ -1342,6 +1738,7 @@ async function prepareLoginConnection(parsed) {
1342
1738
  let forwarded = [...parsed.forwarded];
1343
1739
  let manager = parsed.manager;
1344
1740
  let pair = parsed.pair;
1741
+ let connectionPage = null;
1345
1742
 
1346
1743
  if (shouldLogin) {
1347
1744
  const supabase = await createSupabaseClient();
@@ -1361,9 +1758,15 @@ async function prepareLoginConnection(parsed) {
1361
1758
  slot: parsed.slot,
1362
1759
  startupArgs
1363
1760
  });
1761
+ connectionPage = choice.connectionPage || null;
1364
1762
  if (choice.type === 'pin') {
1365
1763
  manager = choice.manager;
1366
1764
  pair = choice.pair;
1765
+ connectionPage?.update({
1766
+ endpointCandidates: choice.endpointCandidates || [],
1767
+ manager,
1768
+ message: `Connected to LiveDesk Hub at ${manager}.`
1769
+ });
1367
1770
  console.log('Connected by LiveDesk PIN.');
1368
1771
  } else {
1369
1772
  const session = choice.session;
@@ -1372,6 +1775,11 @@ async function prepareLoginConnection(parsed) {
1372
1775
  const resolved = await waitForManagerFromSupabase(supabase);
1373
1776
  manager = resolved.manager;
1374
1777
  pair = resolved.pair;
1778
+ connectionPage?.update({
1779
+ endpointCandidates: resolved.endpointCandidates || [],
1780
+ manager,
1781
+ message: `Connected to LiveDesk Hub at ${manager}.`
1782
+ });
1375
1783
  }
1376
1784
  console.log(`Found LiveDesk Hub at ${manager}.`);
1377
1785
  }
@@ -1394,6 +1802,7 @@ async function prepareLoginConnection(parsed) {
1394
1802
  manager,
1395
1803
  pair,
1396
1804
  slot,
1805
+ connectionPage,
1397
1806
  forwarded,
1398
1807
  rediscoverOnInvalidPair: shouldLogin
1399
1808
  };
@@ -1674,6 +2083,7 @@ async function main() {
1674
2083
  result = await spawnAgent(fastLaunch.command, [...fastLaunch.argsPrefix, ...fastArgs], buildFastEnvironment());
1675
2084
  } else if (prepared.engine === 'fast') {
1676
2085
  console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
2086
+ prepared.connectionPage?.close?.();
1677
2087
  process.exit(2);
1678
2088
  } else {
1679
2089
  console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
@@ -1682,6 +2092,7 @@ async function main() {
1682
2092
  } else {
1683
2093
  result = await spawnAgent(process.execPath, [nodeAgentPath, ...prepared.forwarded]);
1684
2094
  }
2095
+ prepared.connectionPage?.close?.();
1685
2096
  if (result?.signal) {
1686
2097
  process.kill(process.pid, result.signal);
1687
2098
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {