const SESSION_KEY = "geo.ops-web.session"; const PREFERENCE_KEY = "geo.ops-web.login-preference"; export interface OperatorView { id: number; username: string; display_name: string; email: string | null; role: string; status: string; } export interface OpsSession { accessToken: string | null; expiresAt: number | null; operator: OperatorView | null; } export interface LoginPreference { remember: boolean; lastUsername: string; } export interface StoredSession { session: OpsSession; persistent: boolean; } function emptySession(): OpsSession { return { accessToken: null, expiresAt: null, operator: null }; } function emptyPreference(): LoginPreference { return { remember: false, lastUsername: "" }; } function hasWindow(): boolean { return typeof window !== "undefined"; } function readFrom(storage: Storage): OpsSession | null { try { const raw = storage.getItem(SESSION_KEY); if (!raw) return null; const parsed = JSON.parse(raw) as Partial; if (typeof parsed.accessToken !== "string" || !parsed.accessToken) return null; return { accessToken: parsed.accessToken, expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null, operator: parsed.operator && typeof parsed.operator === "object" ? (parsed.operator as OperatorView) : null, }; } catch { return null; } } export function readStoredSession(): StoredSession { if (!hasWindow()) return { session: emptySession(), persistent: false }; const persistent = readFrom(window.localStorage); if (persistent) return { session: persistent, persistent: true }; const ephemeral = readFrom(window.sessionStorage); if (ephemeral) return { session: ephemeral, persistent: false }; return { session: emptySession(), persistent: false }; } export function writeStoredSession(session: OpsSession, persistent: boolean): void { if (!hasWindow()) return; const target = persistent ? window.localStorage : window.sessionStorage; const other = persistent ? window.sessionStorage : window.localStorage; try { target.setItem(SESSION_KEY, JSON.stringify(session)); } catch { // ignore quota / serialization errors } try { other.removeItem(SESSION_KEY); } catch { // noop } } export function clearStoredSession(): void { if (!hasWindow()) return; try { window.localStorage.removeItem(SESSION_KEY); } catch { // noop } try { window.sessionStorage.removeItem(SESSION_KEY); } catch { // noop } } export function readLoginPreference(): LoginPreference { if (!hasWindow()) return emptyPreference(); try { const raw = window.localStorage.getItem(PREFERENCE_KEY); if (!raw) return emptyPreference(); const parsed = JSON.parse(raw) as Partial; return { remember: parsed.remember === true, lastUsername: typeof parsed.lastUsername === "string" ? parsed.lastUsername : "", }; } catch { return emptyPreference(); } } export function writeLoginPreference(pref: LoginPreference): void { if (!hasWindow()) return; try { window.localStorage.setItem(PREFERENCE_KEY, JSON.stringify(pref)); } catch { // noop } }