/** * 服务端认证错误处理工具 * * 用于在 Remix loader/action 中统一处理 AuthenticationError(token 过期) */ import { redirect } from "@remix-run/node"; /** * 包装服务端异步函数,自动处理 AuthenticationError * * @param fn - 需要执行的异步函数 * @param pathname - 当前路径(用于保存重定向目标) * @returns 函数执行结果,或在 token 过期时重定向到登录页 * * @example * // 在 loader 中使用 * export async function loader({ request }: LoaderFunctionArgs) { * const url = new URL(request.url); * * return handleServerAuth(async () => { * const { getUserSession } = await import("~/api/login/auth.server"); * const { frontendJWT } = await getUserSession(request); * * // 调用需要认证的 API * const response = await getDocumentTypes({}, frontendJWT); * * return Response.json({ data: response.data }); * }, url.pathname); * } */ export async function handleServerAuth( fn: () => Promise, pathname: string ): Promise { try { return await fn(); } catch (error) { // 检查是否是 AuthenticationError(token 过期) if (error instanceof Error && error.name === 'AuthenticationError') { console.warn(`⚠️ [Server Auth Handler] Token 过期,重定向到登录页 (from: ${pathname})`); // 保存当前路径,登录后可以跳转回来 const redirectTo = pathname !== '/login' ? pathname : '/'; throw redirect(`/login?redirect=${encodeURIComponent(redirectTo)}`); } // 其他错误继续抛出 throw error; } } /** * 检查错误是否是 AuthenticationError * * @param error - 要检查的错误对象 * @returns 是否是 AuthenticationError */ export function isAuthenticationError(error: unknown): boolean { return error instanceof Error && error.name === 'AuthenticationError'; } /** * 从 AuthenticationError 创建登录重定向 * * @param pathname - 当前路径 * @returns Remix redirect Response */ export function redirectToLogin(pathname: string) { const redirectTo = pathname !== '/login' ? pathname : '/'; return redirect(`/login?redirect=${encodeURIComponent(redirectTo)}`); }