"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { getValidatedAuthSession } from "@/lib/auth-session"; import { getDefaultRouteForRole, type AuthRole, type StoredAuthSession, } from "@/store/auth"; type UseAuthGuardResult = { isCheckingAuth: boolean; session: StoredAuthSession | null; }; /** * 权限 / 登录态的兜底同步间隔。 * * ⚠️ 原来是 5 秒一次 **force** 校验(绕过 10 秒本地缓存,每次都实打服务端), * 这是全站最重的轮询接口 —— 线上实测 /auth/session 约 375 req/s,占全站请求 20%。 * 而权限和登录态本来就不会 5 秒一变,30 秒完全够用; * 另外下面还监听 focus / visibilitychange,用户切回标签页会**立刻**强制校验一次, * 所以真实感知比 30 秒更灵敏。 */ const AUTH_PERMISSION_SYNC_INTERVAL_MS = 30_000; function sameSession(left: StoredAuthSession | null, right: StoredAuthSession) { return Boolean( left && left.key === right.key && left.role === right.role && left.subjectId === right.subjectId && left.name === right.name && left.accountPoolEnabled === right.accountPoolEnabled && left.imageMemoryButtonEnabled === right.imageMemoryButtonEnabled && left.imageShareEvidenceEnabled === right.imageShareEvidenceEnabled && left.wechatOpenid === right.wechatOpenid && left.expiresAt === right.expiresAt, ); } export function useAuthGuard(allowedRoles?: AuthRole[]): UseAuthGuardResult { const router = useRouter(); const [session, setSession] = useState(null); const [isCheckingAuth, setIsCheckingAuth] = useState(true); const allowedRolesKey = (allowedRoles || []).join(","); useEffect(() => { let active = true; const load = async (force = false) => { const roleList = allowedRolesKey ? (allowedRolesKey.split(",") as AuthRole[]) : []; const storedSession = await getValidatedAuthSession({ force }); if (!active) { return; } if (!storedSession) { setSession(null); setIsCheckingAuth(false); router.replace("/login"); return; } if (roleList.length > 0 && !roleList.includes(storedSession.role)) { setSession(storedSession); setIsCheckingAuth(false); router.replace(getDefaultRouteForRole(storedSession.role)); return; } setSession((current) => sameSession(current, storedSession) ? current : storedSession); setIsCheckingAuth(false); }; void load(); const syncPermissions = () => { if (document.visibilityState === "visible") { void load(true); } }; const intervalId = window.setInterval(syncPermissions, AUTH_PERMISSION_SYNC_INTERVAL_MS); window.addEventListener("focus", syncPermissions); document.addEventListener("visibilitychange", syncPermissions); return () => { active = false; window.clearInterval(intervalId); window.removeEventListener("focus", syncPermissions); document.removeEventListener("visibilitychange", syncPermissions); }; }, [allowedRolesKey, router]); return { isCheckingAuth, session }; } export function useRedirectIfAuthenticated() { const router = useRouter(); const [isCheckingAuth, setIsCheckingAuth] = useState(true); useEffect(() => { let active = true; const load = async () => { const storedSession = await getValidatedAuthSession(); if (!active) { return; } if (storedSession) { router.replace(getDefaultRouteForRole(storedSession.role)); return; } setIsCheckingAuth(false); }; void load(); return () => { active = false; }; }, [router]); return { isCheckingAuth }; }