Preserve native sessions during temporary server outages
This commit is contained in:
+36
-6
@@ -1,6 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { sessionResponseState, sessionRetryDelay } from './session-recovery.js';
|
||||
|
||||
let sessionRestoreTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let sessionRestoreAttempt = 0;
|
||||
let sessionRestoreGeneration = 0;
|
||||
|
||||
type ClientSettings = { server_address: string; username: string };
|
||||
|
||||
@@ -63,30 +68,51 @@
|
||||
|
||||
async function restoreNativeSession() {
|
||||
if (!nativeClientAvailable() || !username || !serverAddress) return;
|
||||
const generation = ++sessionRestoreGeneration;
|
||||
clearTimeout(sessionRestoreTimer);
|
||||
let address = serverAddress;
|
||||
const account = username;
|
||||
const isCurrentRestore = () => generation === sessionRestoreGeneration && address === serverAddress && account === username;
|
||||
try {
|
||||
const safeServerAddress = validatedServerAddress(serverAddress);
|
||||
serverAddress = safeServerAddress;
|
||||
address = safeServerAddress;
|
||||
const settings: ClientSettings = { server_address: safeServerAddress, username };
|
||||
const storedSession = await invoke<string | null>('load_server_session', { settings });
|
||||
if (!storedSession) return;
|
||||
if (!storedSession || !isCurrentRestore()) return;
|
||||
const response = await fetch(`${safeServerAddress}/api/v1/auth/session`, {
|
||||
headers: { authorization: `Bearer ${storedSession}` },
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (!isCurrentRestore()) return;
|
||||
const state = sessionResponseState(response.status);
|
||||
if (state === 'revoked') {
|
||||
await invoke('clear_server_session', { settings });
|
||||
if (!isCurrentRestore()) return;
|
||||
sessionToken = '';
|
||||
authenticated = false;
|
||||
authError = 'Authentication revoked. Log in again.';
|
||||
return;
|
||||
}
|
||||
if (state !== 'valid') throw new Error('Session verification unavailable');
|
||||
const result: { must_replace_credentials: boolean } = await response.json();
|
||||
if (!isCurrentRestore()) return;
|
||||
sessionRestoreAttempt = 0;
|
||||
authError = '';
|
||||
sessionToken = storedSession;
|
||||
authenticated = true;
|
||||
mustReplaceCredentials = result.must_replace_credentials;
|
||||
connectionState = 'CONNECTED';
|
||||
await loadAuthenticatedData();
|
||||
await loadAuthenticatedData().catch(() => {
|
||||
authError = 'Authenticated, but dashboard data could not be loaded.';
|
||||
});
|
||||
if (mustReplaceCredentials) newUsername = username;
|
||||
} catch {
|
||||
// A stale keyring session must not prevent the normal login form from opening.
|
||||
sessionToken = '';
|
||||
authenticated = false;
|
||||
if (!isCurrentRestore()) return;
|
||||
// Transport/server failures are not proof of revocation: preserve the keyring.
|
||||
authError = 'Server unavailable. Retrying saved session.';
|
||||
sessionRestoreTimer = setTimeout(() => {
|
||||
if (isCurrentRestore()) void restoreNativeSession();
|
||||
}, sessionRetryDelay(sessionRestoreAttempt++));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +362,8 @@
|
||||
}
|
||||
|
||||
async function login() {
|
||||
sessionRestoreGeneration += 1;
|
||||
clearTimeout(sessionRestoreTimer);
|
||||
if (connectionState === 'INCOMPATIBLE') {
|
||||
authError = 'This client version is not compatible with the server.';
|
||||
return;
|
||||
@@ -449,6 +477,8 @@
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
sessionRestoreGeneration += 1;
|
||||
clearTimeout(sessionRestoreTimer);
|
||||
realtimeSocket?.close();
|
||||
realtimeSocket = undefined;
|
||||
realtimeState = 'DISCONNECTED';
|
||||
|
||||
Reference in New Issue
Block a user