import { reconcileStaffSessionFromToken, useAuthStore, type StaffUser, type StaffUserType, } from '../stores/auth'; let hydratePromise: Promise | null = null; let lastHydrateAt = 0; /** 跨路由复用 /me 结果,减少菜单切换时的重复鉴权请求 */ const HYDRATE_TTL_MS = 60_000; function isStaffUserType(value: unknown): value is StaffUserType { return value === 'ADMIN' || value === 'AGENT'; } export function resetStaffSessionHydration() { hydratePromise = null; lastHydrateAt = 0; } /** 同步判断 session 是否已经新鲜,无需网络请求。主要给 router beforeEach 使用。 */ export function isSessionFresh(): boolean { const auth = useAuthStore(); if (!auth.token.value) return false; return ( lastHydrateAt > 0 && Date.now() - lastHydrateAt < HYDRATE_TTL_MS && hasCompleteStaffUser(auth.user.value) ); } function hasCompleteStaffUser(u: StaffUser | null | undefined): u is StaffUser { return !!(u?.id && u.username && u.userType); } /** Sync manage_user from JWT + /manage/auth/me (fixes stale localStorage userType). */ export async function hydrateStaffSession(): Promise { const auth = useAuthStore(); if (!auth.token.value) return false; const freshEnough = lastHydrateAt > 0 && Date.now() - lastHydrateAt < HYDRATE_TTL_MS && hasCompleteStaffUser(auth.user.value); if (freshEnough) return true; if (hydratePromise) return hydratePromise; hydratePromise = (async () => { reconcileStaffSessionFromToken(); if (!hasCompleteStaffUser(auth.user.value)) { auth.clearStaffSession(); return false; } try { const { default: api } = await import('../api'); const { data } = await api.get('/manage/auth/me'); const raw = data.data as Partial; if (!raw?.id || !raw.username || !isStaffUserType(raw.userType)) { return true; } auth.setSession(auth.token.value, { id: raw.id, username: raw.username, userType: raw.userType, locale: raw.locale, role: raw.role, permissions: Array.isArray(raw.permissions) ? raw.permissions : auth.user.value?.permissions, agentLevel: typeof raw.agentLevel === 'number' ? raw.agentLevel : null, maxAgentLevel: typeof raw.maxAgentLevel === 'number' ? raw.maxAgentLevel : null, canManageSubAgents: raw.canManageSubAgents === true, inviteCode: raw.inviteCode ?? null, visibleMenus: raw.visibleMenus ?? null, }); lastHydrateAt = Date.now(); return true; } catch (e: unknown) { const status = (e as { response?: { status?: number } })?.response?.status; if (status === 401) { auth.clearStaffSession(); return false; } return hasCompleteStaffUser(auth.user.value); } finally { hydratePromise = null; } })(); return hydratePromise; } /** Run before any authenticated route — JWT reconcile + optional /me refresh. */ export async function ensureStaffSession(): Promise { reconcileStaffSessionFromToken(); const auth = useAuthStore(); if (!auth.token.value) return false; if (!hasCompleteStaffUser(auth.user.value)) { reconcileStaffSessionFromToken(); } if (!hasCompleteStaffUser(auth.user.value)) { return false; } return hydrateStaffSession(); }