"use client"; import localforage from "localforage"; export type AuthRole = "admin" | "user" | "normal"; export type StoredAuthSession = { key: string; role: AuthRole; subjectId: string; name: string; accountPoolEnabled?: boolean; imageMemoryButtonEnabled?: boolean; imageShareEvidenceEnabled?: boolean; wechatOpenid?: string; expiresAt?: string; }; export const AUTH_KEY_STORAGE_KEY = "chatgpt2api_auth_key"; export const AUTH_SESSION_STORAGE_KEY = "chatgpt2api_auth_session"; const authStorage = localforage.createInstance({ name: "chatgpt2api", storeName: "auth", }); let authSessionClearedInMemory = false; function emitAuthSessionUpdated() { if (typeof window === "undefined") { return; } window.dispatchEvent(new Event("chatgpt2api:auth-session-updated")); } function normalizeSession(value: unknown, fallbackKey = ""): StoredAuthSession | null { if (!value || typeof value !== "object") { return null; } const candidate = value as Partial; const key = String(candidate.key || fallbackKey || "").trim(); const role = candidate.role === "admin" || candidate.role === "user" || candidate.role === "normal" ? candidate.role : null; if (!key || !role) { return null; } return { key, role, subjectId: String(candidate.subjectId || "").trim(), name: String(candidate.name || "").trim(), accountPoolEnabled: candidate.accountPoolEnabled === true, imageMemoryButtonEnabled: candidate.imageMemoryButtonEnabled === true, imageShareEvidenceEnabled: candidate.imageShareEvidenceEnabled === true, wechatOpenid: String(candidate.wechatOpenid || "").trim(), expiresAt: String(candidate.expiresAt || "").trim(), }; } function isSessionExpired(session: StoredAuthSession | null) { if (!session?.expiresAt) { return false; } const expiresAt = new Date(session.expiresAt).getTime(); return Number.isFinite(expiresAt) && expiresAt <= Date.now(); } export function buildSessionExpiresAt(durationHours: unknown) { const hours = Number(durationHours); if (!Number.isFinite(hours) || hours <= 0) { return ""; } return new Date(Date.now() + hours * 60 * 60 * 1000).toISOString(); } export function getDefaultRouteForRole(role: AuthRole) { return role === "admin" ? "/accounts" : "/image"; } export async function getStoredAuthKey() { if (typeof window === "undefined") { return ""; } if (authSessionClearedInMemory) { return ""; } const [storedKey, storedSession] = await Promise.all([ authStorage.getItem(AUTH_KEY_STORAGE_KEY), authStorage.getItem(AUTH_SESSION_STORAGE_KEY), ]); const normalizedSession = normalizeSession(storedSession, String(storedKey || "")); if (isSessionExpired(normalizedSession)) { await clearStoredAuthSession(); return ""; } return String(normalizedSession?.key || storedKey || "").trim(); } export async function getStoredAuthSession() { if (typeof window === "undefined") { return null; } if (authSessionClearedInMemory) { return null; } const [storedKey, storedSession] = await Promise.all([ authStorage.getItem(AUTH_KEY_STORAGE_KEY), authStorage.getItem(AUTH_SESSION_STORAGE_KEY), ]); const normalizedSession = normalizeSession(storedSession, String(storedKey || "")); if (normalizedSession) { if (isSessionExpired(normalizedSession)) { await clearStoredAuthSession(); return null; } if (normalizedSession.key !== String(storedKey || "").trim()) { await authStorage.setItem(AUTH_KEY_STORAGE_KEY, normalizedSession.key); } return normalizedSession; } if (String(storedKey || "").trim()) { await clearStoredAuthSession(); } return null; } export async function setStoredAuthSession(session: StoredAuthSession) { const normalizedSession = normalizeSession(session); if (!normalizedSession) { await clearStoredAuthSession(); return; } authSessionClearedInMemory = false; await Promise.all([ authStorage.setItem(AUTH_KEY_STORAGE_KEY, normalizedSession.key), authStorage.setItem(AUTH_SESSION_STORAGE_KEY, normalizedSession), ]); emitAuthSessionUpdated(); } export async function setStoredAuthKey(authKey: string) { const normalizedAuthKey = String(authKey || "").trim(); if (!normalizedAuthKey) { await clearStoredAuthSession(); return; } authSessionClearedInMemory = false; await authStorage.setItem(AUTH_KEY_STORAGE_KEY, normalizedAuthKey); emitAuthSessionUpdated(); } export async function clearStoredAuthSession() { if (typeof window === "undefined") { return; } authSessionClearedInMemory = true; await Promise.all([ authStorage.removeItem(AUTH_KEY_STORAGE_KEY), authStorage.removeItem(AUTH_SESSION_STORAGE_KEY), ]); } export async function clearStoredAuthKey() { await clearStoredAuthSession(); }