109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
import {
|
|
reconcileStaffSessionFromToken,
|
|
useAuthStore,
|
|
type StaffUser,
|
|
type StaffUserType,
|
|
} from '../stores/auth';
|
|
|
|
let hydratePromise: Promise<boolean> | 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<boolean> {
|
|
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<StaffUser>;
|
|
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<boolean> {
|
|
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();
|
|
}
|