import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import { adminApi, ApiError, setCsrfToken } from "../../api/client"; import type { AdminRole, AuthState } from "../../api/types"; interface AuthContextValue { auth: AuthState; checking: boolean; login: (username: string, password: string, totpCode: string) => Promise; logout: () => Promise; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [auth, setAuth] = useState({ status: "anonymous" }); const [checking, setChecking] = useState(true); const expireSession = useCallback(() => { setCsrfToken(); setAuth({ status: "anonymous" }); }, []); useEffect(() => { let active = true; void adminApi .session() .then((session) => { if ( active && session.authenticated && session.operatorName && isAdminRole(session.role) ) { setAuth({ status: "authenticated", operatorName: session.operatorName, role: session.role, }); } }) .catch((error: unknown) => { if (!(error instanceof ApiError) || error.status !== 401) { window.dispatchEvent( new CustomEvent("admin:toast", { detail: { message: "暂时无法确认登录状态", tone: "error" }, }), ); } }) .finally(() => { if (active) setChecking(false); }); window.addEventListener("admin:unauthorized", expireSession); return () => { active = false; window.removeEventListener("admin:unauthorized", expireSession); }; }, [expireSession]); const login = useCallback( async (username: string, password: string, totpCode: string) => { const response = await adminApi.login(username, password, totpCode); setCsrfToken(response.csrfToken); setAuth({ status: "authenticated", operatorName: response.operatorName, role: response.role, }); }, [], ); const logout = useCallback(async () => { try { await adminApi.logout(); } catch { // 即使服务端退出失败,也立即清理前端认证状态。 } finally { expireSession(); window.history.replaceState(null, "", window.location.pathname); } }, [expireSession]); const value = useMemo( () => ({ auth, checking, login, logout }), [auth, checking, login, logout], ); return {children}; } export function useAuth(): AuthContextValue { const context = useContext(AuthContext); if (!context) throw new Error("useAuth 必须在 AuthProvider 中使用"); return context; } function isAdminRole(role?: AdminRole): role is AdminRole { return role === "SUPER_ADMIN" || role === "SUPPORT" || role === "ANALYST"; }