import { request, unwrapApiBody, type ApiResponse } from "../utils/http/factory"; export type AuthUser = { id: number; username: string; role: string; publicSlug: string | null; nickname: string | null; avatar: string | null; }; type MeResult = { user: AuthUser; }; type SessionResult = { user: AuthUser | null; }; export type AuthSessionState = { initialized: boolean; pending: boolean; loggedIn: boolean; user: AuthUser | null; }; export const AUTH_SESSION_STATE_KEY = "auth:session"; /** 客户端是否已做过与 Cookie 的一次强制对齐(`ensureClientMeSynced`);登出等场景需重置 */ export const AUTH_CLIENT_ME_SYNCED_KEY = "auth:client-me-synced"; export const DEFAULT_AUTH_SESSION_STATE: AuthSessionState = { initialized: false, pending: false, loggedIn: false, user: null, }; function isUnauthorized(error: unknown) { if (typeof error !== "object" || error === null) { return false; } return "statusCode" in error && (error as { statusCode?: number }).statusCode === 401; } export function useAuthSession() { const state = useState(AUTH_SESSION_STATE_KEY, () => ({ ...DEFAULT_AUTH_SESSION_STATE, })); const clientMeSynced = useState(AUTH_CLIENT_ME_SYNCED_KEY, () => false); const applyUser = (user: AuthUser | null) => { state.value.user = user; state.value.loggedIn = Boolean(user); state.value.initialized = true; }; const clear = () => { clientMeSynced.value = false; applyUser(null); }; const refresh = async (force = false) => { if (state.value.initialized && !force) { return state.value.user; } if (state.value.pending) { return state.value.user; } state.value.pending = true; try { const fetcher = import.meta.server ? useRequestFetch() : request; const payload = await fetcher>("/api/auth/me"); const data = unwrapApiBody(payload); applyUser(data.user); return data.user; } catch (error: unknown) { if (isUnauthorized(error)) { clear(); return null; } throw error; } finally { state.value.pending = false; } }; /** * 全站 SPA 生命周期内至多同步一次客户端会话(`GET /api/auth/session`)。 * 无会话 Cookie 时服务端不查库;有 Cookie 时与 `/api/auth/me` 相比少一次额外查询。 */ const ensureClientMeSynced = async (): Promise => { if (import.meta.server || clientMeSynced.value) { return; } try { const payload = await request>("/api/auth/session"); const data = unwrapApiBody(payload); applyUser(data.user); clientMeSynced.value = true; } catch { /* 网络错误等:保留 SSR 状态,下次进入带壳页面可再试 */ } }; return { initialized: computed(() => state.value.initialized), loggedIn: computed(() => state.value.loggedIn), user: computed(() => state.value.user), pending: computed(() => state.value.pending), refresh, clear, ensureClientMeSynced, }; }