@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.
- package/bin/livedesk-client.js +438 -27
- package/package.json +1 -1
package/bin/livedesk-client.js
CHANGED
|
@@ -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 = '
|
|
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
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
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(
|
|
1054
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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;
|