import { useEffect, useState } from "react"; import { platformApiClient, setPlatformApiAuthFailureHandler, setPlatformApiSessionToken } from "../api/client"; import type { AuthSessionResponse, CurrentUserResponse, LoginRequest, RegisterRequest, UserProfileUpdateRequest, UserThemePreferenceRequest, UserThemePreferenceResponse } from "../api/types"; import type { CurrentUserView } from "../contracts/workspace"; import { capabilitiesForRoles, rolesFromBackendRoles } from "../contracts/workspace"; import { readWebRuntimeEnv } from "../schemas/env"; import { defaultThemeBackgroundId, defaultThemePaletteId, loadThemeState } from "../theme/tokens"; const sessionTokenStorageKey = "platform-web.session.apiToken"; const localFallbackEnabled = readWebRuntimeEnv().enableLocalAuthFallback; export const localFallbackUser: CurrentUserView = { id: "local-server-operator", displayName: "Local Server Operator", email: "local.server@example.test", status: "active", roles: ["serverAdmin"], capabilities: capabilitiesForRoles(["serverAdmin"]), profile: { phone: "", qq: "", contactNote: "本地开发回退账号,仅限服务器工作台" }, source: "local" }; export interface AuthFormState { mode: "login" | "register"; pending: boolean; error?: string; success?: string; } export interface SessionState { user?: CurrentUserView; loaded: boolean; authenticated: boolean; authUnavailable: boolean; localFallbackAvailable: boolean; auth: AuthFormState; login: (request: LoginRequest) => Promise; register: (request: RegisterRequest) => Promise; logout: () => Promise; continueWithLocalFallback: () => void; switchAuthMode: (mode: AuthFormState["mode"]) => void; updateProfile: (request: UserProfileUpdateRequest) => Promise; updateThemePreference: (request: UserThemePreferenceRequest) => Promise; } export async function loadCurrentUser(): Promise { const storedToken = readStoredSessionToken(); setPlatformApiSessionToken(storedToken); try { const response = await platformApiClient.getCurrentUser(); return currentUserFromResponse(response, "api"); } catch { persistSessionToken(null); setPlatformApiSessionToken(null); return null; } } export function useSession(): SessionState { const [user, setUser] = useState(); const [loaded, setLoaded] = useState(false); const [authUnavailable, setAuthUnavailable] = useState(false); const [auth, setAuth] = useState({ mode: "login", pending: false }); useEffect(() => { let cancelled = false; void loadCurrentUser().then((currentUser) => { if (cancelled) { return; } setUser(currentUser ?? undefined); setAuthUnavailable(!currentUser); setLoaded(true); }); return () => { cancelled = true; }; }, []); useEffect(() => { setPlatformApiAuthFailureHandler(() => { persistSessionToken(null); setUser(undefined); setAuthUnavailable(false); setAuth({ mode: "login", pending: false, error: "会话已失效,请重新登录。" }); }); return () => setPlatformApiAuthFailureHandler(null); }, []); async function login(request: LoginRequest) { setAuth({ mode: "login", pending: true }); try { const response = await platformApiClient.login(request); finishAuth(response, "登录成功"); } catch (error) { setAuth({ mode: "login", pending: false, error: error instanceof Error ? error.message : "登录失败,请重试。" }); } } async function register(request: RegisterRequest) { setAuth({ mode: "register", pending: true }); try { const response = await platformApiClient.register(request); const nextUser = currentUserFromResponse(response.user, "api"); if (response.status === "authenticated") { setUser(nextUser); persistSessionToken(response.sessionId ?? null); setPlatformApiSessionToken(response.sessionId ?? null); } setAuth({ mode: response.status === "authenticated" ? "login" : "register", pending: false, success: response.message ?? (response.status === "authenticated" ? "注册成功,已进入工作台。" : "注册申请已提交,等待平台管理员审核。") }); } catch (error) { setAuth({ mode: "register", pending: false, error: error instanceof Error ? error.message : "注册失败,请稍后重试。" }); } } async function logout() { try { await platformApiClient.logout(); } catch { // Local fallback logout still clears the visible session. } persistSessionToken(null); setPlatformApiSessionToken(null); setUser(undefined); setAuthUnavailable(false); setAuth({ mode: "login", pending: false, success: "已退出登录。" }); } function continueWithLocalFallback() { if (!localFallbackEnabled) { setAuth({ mode: "login", pending: false, error: "本地回退未启用,请使用真实账号登录。" }); return; } setUser(localFallbackUser); setAuthUnavailable(false); setAuth({ mode: "login", pending: false, success: "已进入本地回退工作台。" }); } function switchAuthMode(mode: AuthFormState["mode"]) { setAuth({ mode, pending: false }); } async function updateProfile(request: UserProfileUpdateRequest): Promise { const current = user ?? localFallbackUser; try { const response = await platformApiClient.updateCurrentUserProfile(request); const nextUser = currentUserFromResponse(response, "api"); setUser(nextUser); return nextUser; } catch { const nextUser: CurrentUserView = { ...current, displayName: request.displayName, profile: { avatarUrl: request.avatarUrl, phone: request.phone, qq: request.qq, contactNote: request.contactNote }, source: "local" }; setUser(nextUser); return nextUser; } } async function updateThemePreference(request: UserThemePreferenceRequest): Promise { const current = user ?? localFallbackUser; try { const response = await platformApiClient.updateCurrentUserTheme(request); setUser({ ...current, themePreference: response, source: "api" }); return response; } catch { const response: UserThemePreferenceResponse = { ...request, userId: current.id, persistence: "local", updatedAt: new Date().toISOString() }; const nextUser = { ...current, themePreference: response, source: "local" as const }; setUser(nextUser); return response; } } function finishAuth(response: AuthSessionResponse, fallbackMessage: string) { const nextUser = currentUserFromResponse(response.user, "api"); setUser(nextUser); persistSessionToken(response.sessionId ?? null); setPlatformApiSessionToken(response.sessionId ?? null); setAuthUnavailable(false); setAuth({ mode: "login", pending: false, success: response.message ?? fallbackMessage }); } return { user, loaded, authenticated: Boolean(user), authUnavailable, localFallbackAvailable: localFallbackEnabled, auth, login, register, logout, continueWithLocalFallback, switchAuthMode, updateProfile, updateThemePreference }; } export function currentUserFromResponse(response: CurrentUserResponse, source: CurrentUserView["source"]): CurrentUserView { const roles = rolesFromBackendRoles(response.roles); const themeState = loadThemeState(); return { id: response.id, displayName: response.displayName, email: response.email, status: response.status ?? "active", roles, capabilities: response.capabilities?.length ? (response.capabilities as CurrentUserView["capabilities"]) : capabilitiesForRoles(roles), profile: response.profile ?? {}, themePreference: response.themePreference ?? (source === "local" ? { userId: response.id, paletteId: themeState.paletteId || defaultThemePaletteId, backgroundPresetId: themeState.backgroundPresetId || defaultThemeBackgroundId, backgroundImage: themeState.backgroundImage, persistence: "local", updatedAt: new Date().toISOString() } : undefined), source }; } function readStoredSessionToken(): string | null { if (typeof window === "undefined") { return null; } try { return window.localStorage.getItem(sessionTokenStorageKey); } catch { return null; } } function persistSessionToken(token: string | null) { if (typeof window === "undefined") { return; } try { if (token) { window.localStorage.setItem(sessionTokenStorageKey, token); } else { window.localStorage.removeItem(sessionTokenStorageKey); } } catch { // Storage can be unavailable; the in-memory session remains usable. } }