import { enterKeychainMode, hasKeychainShell, KeychainBoot, KeychainSessionTokens, } from './sessions'; import { KEYCHAIN_TIMEOUT_MS, KeychainInvoke } from './keychainMirror'; /** * [Gitea #105] Desktop, step 2: at boot, before the app renders, read the login * tokens from the OS keychain into memory (see sessions.ts). Returns: * - `off`: not the desktop app, or no keychain here (Linux, older builds) — * localStorage stays authoritative, exactly as before; * - a KeychainBoot from enterKeychainMode: `keychain` (go on), or `missing` * (localStorage says the tokens are in the keychain but they can't be read; * the caller shows Retry / Sign in again and must NOT start the mirror, * which would clear the keychain entry it couldn't read). */ export type KeychainBootResult = { mode: 'off' } | KeychainBoot | { mode: 'error'; error: string }; const withTimeout = (p: Promise, ms: number): Promise => new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error('keychain timed out')), ms); p.then( (v) => { clearTimeout(timer); resolve(v); }, (e) => { clearTimeout(timer); reject(e); }, ); }); const asTokens = (v: unknown): KeychainSessionTokens | null => { if (!v || typeof v !== 'object') return null; const t = v as Partial; if (typeof t.userId !== 'string' || typeof t.deviceId !== 'string') return null; if (typeof t.accessToken !== 'string') return null; return { userId: t.userId, deviceId: t.deviceId, accessToken: t.accessToken, ...(typeof t.refreshToken === 'string' ? { refreshToken: t.refreshToken } : {}), }; }; export async function bootKeychainSession( invoke: KeychainInvoke | undefined, timeoutMs = KEYCHAIN_TIMEOUT_MS, ): Promise { if (!invoke) return { mode: 'off' }; let supported: unknown; try { supported = await withTimeout(invoke('secure_session_supported'), timeoutMs); } catch { supported = false; } if (supported !== true) { // A shell without a keychain to read from can only mean the store went // away (e.g. a downgrade of the desktop build): say so, don't log out. return hasKeychainShell() ? { mode: 'error', error: 'keychain not available' } : { mode: 'off' }; } let stored: unknown; try { stored = await withTimeout(invoke('secure_session_get'), timeoutMs); } catch (e) { if (hasKeychainShell()) return { mode: 'error', error: (e instanceof Error ? e.message : String(e)).slice(0, 200) }; return { mode: 'off' }; } return enterKeychainMode(asTokens(stored)); }