@livedesk/client 0.1.187 → 0.1.189
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/README.md +3 -2
- package/bin/livedesk-client-node.js +8 -13
- package/bin/livedesk-client.js +4 -1
- package/package.json +5 -5
- package/src/runtime/client-runtime-server.js +128 -22
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ callback exchange, and saved authentication as one path. The browser only
|
|
|
22
22
|
shows progress; it does not maintain a second session that must be transferred
|
|
23
23
|
back to the Client. LiveDesk then waits for the active Hub published by the
|
|
24
24
|
same account and connects locally. If the Hub is not
|
|
25
|
-
ready yet, the client waits for a Hub-online event and
|
|
26
|
-
|
|
25
|
+
ready yet, the client waits for a Hub-online event and polls the registry at
|
|
26
|
+
most every six seconds as a fallback. Missing a wake event can never leave a
|
|
27
|
+
Client asleep for minutes. If a
|
|
27
28
|
connected Hub is replaced, the launcher also leaves the stale endpoint and
|
|
28
29
|
discovers the newly published Hub without another Google login. Omit the number
|
|
29
30
|
for first-available placement, or pass `1` to `999` to pin this machine to a
|
|
@@ -58,16 +58,10 @@ function getClientUpdateNeutralCwd(operationId) {
|
|
|
58
58
|
async function prepareClientUpdateNeutralCwd(operationId) {
|
|
59
59
|
const neutralCwd = path.resolve(getClientUpdateNeutralCwd(operationId));
|
|
60
60
|
await fs.mkdir(neutralCwd, { recursive: true });
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
].find(candidate => existsSync(candidate));
|
|
66
|
-
if (shadowPath) {
|
|
67
|
-
throw new Error(`LiveDesk update neutral working directory is shadowed by ${shadowPath}: ${neutralCwd}`);
|
|
68
|
-
}
|
|
69
|
-
const parent = path.dirname(ancestor);
|
|
70
|
-
if (parent === ancestor) break;
|
|
61
|
+
const unexpectedEntry = (await fs.readdir(neutralCwd))[0];
|
|
62
|
+
if (unexpectedEntry) {
|
|
63
|
+
const shadowPath = path.join(neutralCwd, unexpectedEntry);
|
|
64
|
+
throw new Error(`LiveDesk update neutral working directory is shadowed by ${shadowPath}: ${neutralCwd}`);
|
|
71
65
|
}
|
|
72
66
|
return neutralCwd;
|
|
73
67
|
}
|
|
@@ -89,6 +83,7 @@ function buildClientUpdateNpxEnvironment(baseEnv, neutralCwd) {
|
|
|
89
83
|
}
|
|
90
84
|
env.INIT_CWD = neutralCwd;
|
|
91
85
|
env.npm_config_local_prefix = neutralCwd;
|
|
86
|
+
env.npm_config_workspaces = 'false';
|
|
92
87
|
env.npm_config_include_workspace_root = 'false';
|
|
93
88
|
return env;
|
|
94
89
|
}
|
|
@@ -1583,13 +1578,13 @@ function buildClientUpdateBootstrapScript() {
|
|
|
1583
1578
|
'const terminateTree = async child => { const pid = Number(child?.pid || 0); if (pid <= 1) return; if (process.platform === "win32") { await new Promise(resolve => { const killer = spawn("taskkill.exe", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" }); let done = false; const finish = () => { if (done) return; done = true; clearTimeout(timer); resolve(); }; const timer = setTimeout(() => { try { killer.kill("SIGKILL"); } catch {} finish(); }, 10000); killer.once("error", finish); killer.once("exit", finish); }); if (isAlive(pid)) { try { child.kill("SIGKILL"); } catch {} } return; } try { process.kill(-pid, "SIGTERM"); } catch { try { child.kill("SIGTERM"); } catch {} } const gracefulDeadline = Date.now() + 2000; while (Date.now() < gracefulDeadline && (isGroupAlive(pid) || isAlive(pid))) await sleep(100); if (isGroupAlive(pid) || isAlive(pid)) { try { process.kill(-pid, "SIGKILL"); } catch { try { child.kill("SIGKILL"); } catch {} } } };',
|
|
1584
1579
|
'const writeHandoffStarted = () => withStateLock(() => { if (deadlineExpired()) throw new Error("The absolute LiveDesk Client update deadline expired before handoff."); const previous = readState(); if (previous.operationId !== operationId) throw new Error("LiveDesk update handoff was superseded before exact-package launch."); const now = new Date().toISOString(); const next = { ...previous, operationId, stage: "handoff-started", starterPid: process.pid, updateDeadlineEpochMs, restartVerified: false, error: "", updatedAt: now }; const temporary = statePath + "." + process.pid + ".handoff-started.tmp"; fs.writeFileSync(temporary, JSON.stringify(next, null, 2), { encoding: "utf8", mode: 0o600 }); fs.renameSync(temporary, statePath); });',
|
|
1585
1580
|
'const monitorPreflight = child => { let settled = false; const finish = (error, preserveFailure = false) => { if (settled) return; settled = true; clearInterval(poll); clearTimeout(timeout); child.unref(); if (preserveFailure) process.exitCode = 1; else if (error) writeFailure(error); }; const inspect = () => { const state = readState(); if (state.operationId !== operationId) return; if (state.stage === "preflight-ready" || state.stage === "waiting-for-shutdown") finish(); else if (state.stage === "failed") finish(null, true); }; const cancelTimedOutHandoff = async () => { const error = new Error(deadlineExpired() ? "The absolute LiveDesk Client update deadline expired before exact-package preflight." : "Timed out waiting for exact-package update preflight."); while (!settled) { inspect(); if (settled) return; let outcome; try { outcome = writeCancellation(error); } catch (lockError) { finish(lockError); return; } if (outcome === "ready" || outcome === "failed") { inspect(); if (settled) return; } else if (outcome === "cancelled" || outcome === "superseded") { await terminateTree(child); finish(null, true); return; } await sleep(25); } }; const poll = setInterval(inspect, 100); const timeoutMs = Math.max(1, Math.min(Math.max(1000, Number(process.env.LIVEDESK_CLIENT_UPDATE_HANDOFF_TIMEOUT_MS || 140000)), updateDeadlineEpochMs - Date.now())); const timeout = setTimeout(() => { void cancelTimedOutHandoff(); }, timeoutMs); child.once("exit", (code, signal) => { inspect(); if (!settled) finish(new Error("Exact-package update supervisor exited before preflight (code=" + (code ?? "none") + ", signal=" + (signal || "none") + ").")); }); inspect(); };',
|
|
1586
|
-
'const prepareNeutralCwd = () => { if (!neutralCwd) throw new Error("LiveDesk update neutral working directory is unavailable"); fs.mkdirSync(neutralCwd, { recursive: true });
|
|
1581
|
+
'const prepareNeutralCwd = () => { if (!neutralCwd) throw new Error("LiveDesk update neutral working directory is unavailable"); fs.mkdirSync(neutralCwd, { recursive: true }); const unexpectedEntry = fs.readdirSync(neutralCwd)[0]; if (unexpectedEntry) { const shadow = path.join(neutralCwd, unexpectedEntry); throw new Error("LiveDesk update neutral working directory is shadowed by " + shadow + ": " + neutralCwd); } };',
|
|
1587
1582
|
'if (!operationId || !versionPattern.test(targetProductVersion) || deadlineExpired()) { writeFailure(new Error("Invalid or expired LiveDesk exact-package update handoff."), true); } else { try { prepareNeutralCwd(); writeHandoffStarted();',
|
|
1588
|
-
'const env = { ...process.env, LIVEDESK_UPDATE_STARTER_PID: String(process.pid) }; const isolated = new Set(["init_cwd", "npm_config_local_prefix", "npm_config_workspace", "npm_config_workspaces", "npm_config_include_workspace_root", "npm_package_json", "npm_lifecycle_event", "npm_lifecycle_script"]); for (const key of Object.keys(env)) if (isolated.has(key.toLowerCase())) delete env[key]; env.INIT_CWD = neutralCwd; env.npm_config_local_prefix = neutralCwd; env.npm_config_include_workspace_root = "false";',
|
|
1583
|
+
'const env = { ...process.env, LIVEDESK_UPDATE_STARTER_PID: String(process.pid) }; const isolated = new Set(["init_cwd", "npm_config_local_prefix", "npm_config_workspace", "npm_config_workspaces", "npm_config_include_workspace_root", "npm_package_json", "npm_lifecycle_event", "npm_lifecycle_script"]); for (const key of Object.keys(env)) if (isolated.has(key.toLowerCase())) delete env[key]; env.INIT_CWD = neutralCwd; env.npm_config_local_prefix = neutralCwd; env.npm_config_workspaces = "false"; env.npm_config_include_workspace_root = "false";',
|
|
1589
1584
|
'const nodeDir = path.dirname(process.execPath);',
|
|
1590
1585
|
'const npxCli = [env.LIVEDESK_NPX_CLI_PATH, env.npm_execpath ? path.join(path.dirname(env.npm_execpath), "npx-cli.js") : "", env.LIVEDESK_NPX_EXECUTABLE ? path.join(path.dirname(env.LIVEDESK_NPX_EXECUTABLE), "node_modules", "npm", "bin", "npx-cli.js") : "", path.join(nodeDir, "node_modules", "npm", "bin", "npx-cli.js")].find(value => value && fs.existsSync(value));',
|
|
1591
1586
|
'const npx = env.LIVEDESK_NPX_EXECUTABLE || (process.platform === "win32" ? "npx.cmd" : "npx");',
|
|
1592
|
-
'const args = ["-y", "--prefer-online", "livedesk@" + targetProductVersion, "--internal-legacy-client-update"];',
|
|
1587
|
+
'const args = ["-y", "--prefer-online", "--prefix", neutralCwd, "--workspaces=false", "livedesk@" + targetProductVersion, "--internal-legacy-client-update"];',
|
|
1593
1588
|
'const quoteCmd = value => "\\"" + String(value).replaceAll("%", "%%").replaceAll("\\"", "\\"\\"") + "\\"";',
|
|
1594
1589
|
'const invocation = npxCli ? { command: process.execPath, args: [npxCli, ...args] } : process.platform === "win32" ? { command: env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", "call " + quoteCmd(npx) + " " + args.map(quoteCmd).join(" ")] } : { command: npx, args };',
|
|
1595
1590
|
'if (!npxCli && path.isAbsolute(npx) && !fs.existsSync(npx)) { writeFailure(new Error("LiveDesk npx executable was not found: " + npx)); } else { try { const child = spawn(invocation.command, invocation.args, { cwd: neutralCwd, env, detached: true, stdio: "ignore", windowsHide: true }); child.once("error", writeFailure); child.once("spawn", () => monitorPreflight(child)); } catch (error) { writeFailure(error); } }',
|
package/bin/livedesk-client.js
CHANGED
|
@@ -30,7 +30,10 @@ const DEFAULT_HUB_CLIENT_PORT = 5197;
|
|
|
30
30
|
const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
|
|
31
31
|
const DEFAULT_AUTH_CALLBACK_PORT = 5179;
|
|
32
32
|
const ENDPOINT_PROBE_TIMEOUT_MS = 900;
|
|
33
|
-
|
|
33
|
+
// Hub-online delivery is an optimization, not a correctness dependency. A
|
|
34
|
+
// browser, proxy, or suspended network can miss the wake event, so registry
|
|
35
|
+
// polling must stay interactive instead of backing off to multi-minute waits.
|
|
36
|
+
const DISCOVERY_RETRY_SCHEDULE_MS = [750, 750, 750, 750, 750, 750, 750, 750, 5000];
|
|
34
37
|
const DISCOVERY_RETRY_JITTER_RATIO = 0.2;
|
|
35
38
|
const HUB_WAKE_QUERY_JITTER_MAX_MS = 2000;
|
|
36
39
|
const EXIT_INVALID_PAIR_TOKEN = 23;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.189",
|
|
4
4
|
"description": "LiveDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,10 +41,10 @@
|
|
|
41
41
|
"ws": "^8.18.3"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
45
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
47
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
44
|
+
"@livedesk/fast-linux-x64": "0.1.396",
|
|
45
|
+
"@livedesk/fast-osx-arm64": "0.1.396",
|
|
46
|
+
"@livedesk/fast-osx-x64": "0.1.396",
|
|
47
|
+
"@livedesk/fast-win-x64": "0.1.396"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|
|
@@ -34,10 +34,84 @@ const READ_ONLY_CLIENT_API_PATHS = new Set([
|
|
|
34
34
|
'/api/auth/status'
|
|
35
35
|
]);
|
|
36
36
|
|
|
37
|
-
function normalizeString(value, maxLength = 1024) {
|
|
38
|
-
return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
|
|
39
|
-
}
|
|
40
|
-
|
|
37
|
+
function normalizeString(value, maxLength = 1024) {
|
|
38
|
+
return String(value ?? '').replace(/[\r\n\t]/g, ' ').trim().slice(0, maxLength);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeAccountAvatarUrl(value) {
|
|
42
|
+
const candidate = normalizeString(value, 2048);
|
|
43
|
+
if (!candidate) return '';
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(candidate);
|
|
46
|
+
return url.protocol === 'https:' || url.protocol === 'http:' ? url.toString() : '';
|
|
47
|
+
} catch {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readClientAccountProfile(sessionOrUser) {
|
|
53
|
+
const user = sessionOrUser?.user && typeof sessionOrUser.user === 'object'
|
|
54
|
+
? sessionOrUser.user
|
|
55
|
+
: sessionOrUser && typeof sessionOrUser === 'object'
|
|
56
|
+
? sessionOrUser
|
|
57
|
+
: {};
|
|
58
|
+
const metadata = user.user_metadata && typeof user.user_metadata === 'object'
|
|
59
|
+
? user.user_metadata
|
|
60
|
+
: {};
|
|
61
|
+
const email = normalizeString(user.email, 320);
|
|
62
|
+
const name = normalizeString(
|
|
63
|
+
metadata.full_name
|
|
64
|
+
|| metadata.name
|
|
65
|
+
|| metadata.preferred_username
|
|
66
|
+
|| user.name
|
|
67
|
+
|| email
|
|
68
|
+
|| user.id,
|
|
69
|
+
160
|
|
70
|
+
);
|
|
71
|
+
const avatarUrl = normalizeAccountAvatarUrl(
|
|
72
|
+
metadata.avatar_url
|
|
73
|
+
|| metadata.picture
|
|
74
|
+
|| user.avatar_url
|
|
75
|
+
|| user.picture
|
|
76
|
+
);
|
|
77
|
+
return { email, name, avatarUrl };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mergeClientAccountProfile(session, sourceUser) {
|
|
81
|
+
const profile = readClientAccountProfile(sourceUser);
|
|
82
|
+
const userMetadata = {
|
|
83
|
+
...(profile.name ? { full_name: profile.name } : {}),
|
|
84
|
+
...(profile.avatarUrl ? { avatar_url: profile.avatarUrl } : {})
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
...session,
|
|
88
|
+
user: {
|
|
89
|
+
...(session?.user || {}),
|
|
90
|
+
...(Object.keys(userMetadata).length > 0 ? { user_metadata: userMetadata } : {})
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function hydrateClientAccountProfile(session) {
|
|
96
|
+
const accessToken = normalizeString(session?.access_token, 8192);
|
|
97
|
+
if (!accessToken) return session;
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(`${SUPABASE_URL.replace(/\/+$/, '')}/auth/v1/user`, {
|
|
100
|
+
headers: {
|
|
101
|
+
apikey: SUPABASE_PUBLISHABLE_KEY,
|
|
102
|
+
Authorization: `Bearer ${accessToken}`,
|
|
103
|
+
Accept: 'application/json'
|
|
104
|
+
},
|
|
105
|
+
signal: AbortSignal.timeout(5_000)
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) return session;
|
|
108
|
+
const verifiedUser = await response.json().catch(() => null);
|
|
109
|
+
return verifiedUser ? mergeClientAccountProfile(session, verifiedUser) : session;
|
|
110
|
+
} catch {
|
|
111
|
+
return session;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
41
115
|
function normalizePort(value, fallback = DEFAULT_PORT) {
|
|
42
116
|
const port = Number(value);
|
|
43
117
|
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : fallback;
|
|
@@ -538,6 +612,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
538
612
|
const appVersion = normalizeString(options.appVersion || process.env.LIVEDESK_NPM_LAUNCHER_VERSION, 80) || 'dev';
|
|
539
613
|
const savedSession = options.savedSession?.refresh_token ? options.savedSession : readSavedSession();
|
|
540
614
|
const savedSessionPersisted = Boolean(savedSession?.refresh_token);
|
|
615
|
+
const savedAccountProfile = readClientAccountProfile(savedSession);
|
|
541
616
|
let previousCpuTotals = readCpuTotals();
|
|
542
617
|
let hardwareSnapshot = { gpus: [], disks: [], collectedAt: '', collecting: true };
|
|
543
618
|
let networkSnapshot = { interfaces: activeNetworkInterfaces(), receivedBytes: 0, sentBytes: 0, receiveBytesPerSecond: 0, sendBytesPerSecond: 0, sampledAt: '' };
|
|
@@ -570,7 +645,9 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
570
645
|
lastResult: '',
|
|
571
646
|
lastError: '',
|
|
572
647
|
persisted: savedSessionPersisted,
|
|
573
|
-
email:
|
|
648
|
+
email: savedAccountProfile.email,
|
|
649
|
+
name: savedAccountProfile.name,
|
|
650
|
+
avatarUrl: savedAccountProfile.avatarUrl
|
|
574
651
|
}
|
|
575
652
|
};
|
|
576
653
|
let completed = false;
|
|
@@ -586,8 +663,8 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
586
663
|
const runtime = createRuntimeManager({
|
|
587
664
|
role: 'client',
|
|
588
665
|
state: RuntimeState.WAITING_AUTH,
|
|
589
|
-
authenticated: Boolean(
|
|
590
|
-
userId:
|
|
666
|
+
authenticated: Boolean(savedSession?.access_token),
|
|
667
|
+
userId: savedSession?.user?.id || '',
|
|
591
668
|
deviceId,
|
|
592
669
|
appVersion,
|
|
593
670
|
startedAt: new Date().toISOString(),
|
|
@@ -673,6 +750,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
673
750
|
lastChoice = choice || lastChoice;
|
|
674
751
|
if (completed) {
|
|
675
752
|
if (choice?.session?.access_token) {
|
|
753
|
+
const accountProfile = readClientAccountProfile(choice.session);
|
|
676
754
|
runtime.setAuthenticated(true, choice.session.user?.id || '');
|
|
677
755
|
state.auth = {
|
|
678
756
|
...state.auth,
|
|
@@ -680,7 +758,9 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
680
758
|
lastResult: 'accepted',
|
|
681
759
|
lastError: '',
|
|
682
760
|
persisted: true,
|
|
683
|
-
email:
|
|
761
|
+
email: accountProfile.email || state.auth.email,
|
|
762
|
+
name: accountProfile.name || state.auth.name,
|
|
763
|
+
avatarUrl: accountProfile.avatarUrl || state.auth.avatarUrl
|
|
684
764
|
};
|
|
685
765
|
}
|
|
686
766
|
return;
|
|
@@ -695,12 +775,15 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
695
775
|
: [];
|
|
696
776
|
state.message = message;
|
|
697
777
|
state.lastError = '';
|
|
778
|
+
const accountProfile = readClientAccountProfile(choice?.session);
|
|
698
779
|
state.auth = {
|
|
699
780
|
...state.auth,
|
|
700
781
|
lastAttemptAt: state.connectedAt,
|
|
701
782
|
lastResult: 'accepted',
|
|
702
783
|
lastError: '',
|
|
703
|
-
email:
|
|
784
|
+
email: accountProfile.email || state.auth.email,
|
|
785
|
+
name: accountProfile.name || state.auth.name,
|
|
786
|
+
avatarUrl: accountProfile.avatarUrl || state.auth.avatarUrl
|
|
704
787
|
};
|
|
705
788
|
runtime.setAuthenticated(Boolean(choice?.session?.access_token), choice?.session?.user?.id || '');
|
|
706
789
|
runtime.update({ state: RuntimeState.RESOLVING_ROLE }, 'credentials-accepted');
|
|
@@ -727,6 +810,8 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
727
810
|
authenticated: snapshot.authenticated,
|
|
728
811
|
userId: snapshot.userId || '',
|
|
729
812
|
userEmail: state.auth.email || '',
|
|
813
|
+
userName: state.auth.name || '',
|
|
814
|
+
userAvatarUrl: state.auth.avatarUrl || '',
|
|
730
815
|
appVersion,
|
|
731
816
|
runtimeId: `client-${snapshot.runtimePid}-${snapshot.startedAt}`,
|
|
732
817
|
runtimePid: snapshot.runtimePid,
|
|
@@ -845,6 +930,8 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
845
930
|
authenticated: snapshot.authenticated,
|
|
846
931
|
userId: snapshot.userId || null,
|
|
847
932
|
userEmail: state.auth.email || null,
|
|
933
|
+
userName: state.auth.name || null,
|
|
934
|
+
userAvatarUrl: state.auth.avatarUrl || null,
|
|
848
935
|
role: 'client'
|
|
849
936
|
});
|
|
850
937
|
return;
|
|
@@ -896,7 +983,10 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
896
983
|
const sessionCandidate = exchanged?.session || exchanged;
|
|
897
984
|
const normalized = normalizeRuntimeAuthSession(sessionCandidate, { requireRefreshToken: true });
|
|
898
985
|
if (!normalized.ok) throw new Error(normalized.error);
|
|
899
|
-
const session = {
|
|
986
|
+
const session = {
|
|
987
|
+
...sessionCandidate,
|
|
988
|
+
...mergeClientAccountProfile(normalized.session, sessionCandidate?.user)
|
|
989
|
+
};
|
|
900
990
|
if (!writeSavedSession(session)) throw new Error('refresh-token-required');
|
|
901
991
|
state.auth.persisted = true;
|
|
902
992
|
complete({ type: 'google', session }, 'Signed in. Finding the LiveDesk Hub.');
|
|
@@ -928,11 +1018,15 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
928
1018
|
respondJson(401, { ok: false, error: `supabase-user-verification-failed:${verifyResponse.status}` });
|
|
929
1019
|
return;
|
|
930
1020
|
}
|
|
931
|
-
const verifiedUser = await verifyResponse.json().catch(() => ({}));
|
|
932
|
-
const
|
|
1021
|
+
const verifiedUser = await verifyResponse.json().catch(() => ({}));
|
|
1022
|
+
const verifiedSession = {
|
|
933
1023
|
...normalized.session,
|
|
934
|
-
user: {
|
|
1024
|
+
user: {
|
|
1025
|
+
id: normalizeString(verifiedUser?.id || body.user?.id || body.userId, 160),
|
|
1026
|
+
email: normalizeString(verifiedUser?.email || body.user?.email || body.email, 320)
|
|
1027
|
+
}
|
|
935
1028
|
};
|
|
1029
|
+
const session = mergeClientAccountProfile(verifiedSession, verifiedUser);
|
|
936
1030
|
try {
|
|
937
1031
|
if (!writeSavedSession(session)) throw new Error('refresh-token-required');
|
|
938
1032
|
state.auth.persisted = true;
|
|
@@ -954,7 +1048,15 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
954
1048
|
state.manager = '';
|
|
955
1049
|
state.message = 'Saved sign-in was cleared. Sign in again to start the Client.';
|
|
956
1050
|
state.lastError = '';
|
|
957
|
-
state.auth = {
|
|
1051
|
+
state.auth = {
|
|
1052
|
+
lastAttemptAt: new Date().toISOString(),
|
|
1053
|
+
lastResult: 'cleared',
|
|
1054
|
+
lastError: '',
|
|
1055
|
+
persisted: false,
|
|
1056
|
+
email: '',
|
|
1057
|
+
name: '',
|
|
1058
|
+
avatarUrl: ''
|
|
1059
|
+
};
|
|
958
1060
|
runtime.setAuthenticated(false);
|
|
959
1061
|
respondJson(200, { ok: true, authenticated: false, role: 'client' });
|
|
960
1062
|
return;
|
|
@@ -1168,16 +1270,20 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1168
1270
|
complete(initialChoice, initialChoiceMessage);
|
|
1169
1271
|
});
|
|
1170
1272
|
} else {
|
|
1171
|
-
const saved = normalizeRuntimeAuthSession(
|
|
1273
|
+
const saved = normalizeRuntimeAuthSession(savedSession, { requireRefreshToken: true });
|
|
1172
1274
|
if (saved.ok) {
|
|
1173
1275
|
setImmediate(() => {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1276
|
+
void (async () => {
|
|
1277
|
+
try {
|
|
1278
|
+
const normalizedSession = mergeClientAccountProfile(saved.session, savedSession?.user);
|
|
1279
|
+
const hydratedSession = await hydrateClientAccountProfile(normalizedSession);
|
|
1280
|
+
if (!writeSavedSession(hydratedSession)) throw new Error('refresh-token-required');
|
|
1281
|
+
state.auth.persisted = true;
|
|
1282
|
+
complete({ type: 'google', session: hydratedSession }, 'Saved sign-in found. Finding the LiveDesk Hub.');
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
recordAuthAttempt('rejected', `session-persistence-failed:${normalizeString(error?.message || error, 160)}`);
|
|
1285
|
+
}
|
|
1286
|
+
})();
|
|
1181
1287
|
});
|
|
1182
1288
|
}
|
|
1183
1289
|
}
|