2026-04-29 15:59:23 +08:00
|
|
|
const SESSION_KEY = "geo.ops-web.session";
|
|
|
|
|
const PREFERENCE_KEY = "geo.ops-web.login-preference";
|
2026-04-28 11:33:17 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 15:59:23 +08:00
|
|
|
export interface LoginPreference {
|
|
|
|
|
remember: boolean;
|
|
|
|
|
lastUsername: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface StoredSession {
|
|
|
|
|
session: OpsSession;
|
|
|
|
|
persistent: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 11:33:17 +08:00
|
|
|
function emptySession(): OpsSession {
|
|
|
|
|
return { accessToken: null, expiresAt: null, operator: null };
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 15:59:23 +08:00
|
|
|
function emptyPreference(): LoginPreference {
|
|
|
|
|
return { remember: false, lastUsername: "" };
|
2026-04-28 11:33:17 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-29 15:59:23 +08:00
|
|
|
function hasWindow(): boolean {
|
|
|
|
|
return typeof window !== "undefined";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readFrom(storage: Storage): OpsSession | null {
|
2026-04-28 11:33:17 +08:00
|
|
|
try {
|
2026-04-29 15:59:23 +08:00
|
|
|
const raw = storage.getItem(SESSION_KEY);
|
|
|
|
|
if (!raw) return null;
|
2026-04-28 11:33:17 +08:00
|
|
|
const parsed = JSON.parse(raw) as Partial<OpsSession>;
|
2026-04-29 15:59:23 +08:00
|
|
|
if (typeof parsed.accessToken !== "string" || !parsed.accessToken) return null;
|
2026-04-28 11:33:17 +08:00
|
|
|
return {
|
2026-04-29 15:59:23 +08:00
|
|
|
accessToken: parsed.accessToken,
|
2026-04-28 11:33:17 +08:00
|
|
|
expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null,
|
|
|
|
|
operator:
|
|
|
|
|
parsed.operator && typeof parsed.operator === "object"
|
|
|
|
|
? (parsed.operator as OperatorView)
|
|
|
|
|
: null,
|
|
|
|
|
};
|
|
|
|
|
} catch {
|
2026-04-29 15:59:23 +08:00
|
|
|
return null;
|
2026-04-28 11:33:17 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 15:59:23 +08:00
|
|
|
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;
|
2026-04-28 11:33:17 +08:00
|
|
|
try {
|
2026-04-29 15:59:23 +08:00
|
|
|
target.setItem(SESSION_KEY, JSON.stringify(session));
|
2026-04-28 11:33:17 +08:00
|
|
|
} catch {
|
|
|
|
|
// ignore quota / serialization errors
|
|
|
|
|
}
|
2026-04-29 15:59:23 +08:00
|
|
|
try {
|
|
|
|
|
other.removeItem(SESSION_KEY);
|
|
|
|
|
} catch {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
2026-04-28 11:33:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function clearStoredSession(): void {
|
2026-04-29 15:59:23 +08:00
|
|
|
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<LoginPreference>;
|
|
|
|
|
return {
|
|
|
|
|
remember: parsed.remember === true,
|
|
|
|
|
lastUsername: typeof parsed.lastUsername === "string" ? parsed.lastUsername : "",
|
|
|
|
|
};
|
|
|
|
|
} catch {
|
|
|
|
|
return emptyPreference();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function writeLoginPreference(pref: LoginPreference): void {
|
|
|
|
|
if (!hasWindow()) return;
|
2026-04-28 11:33:17 +08:00
|
|
|
try {
|
2026-04-29 15:59:23 +08:00
|
|
|
window.localStorage.setItem(PREFERENCE_KEY, JSON.stringify(pref));
|
2026-04-28 11:33:17 +08:00
|
|
|
} catch {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
}
|