"use client"; import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { toast } from "@heroui/react"; import { signOut, useSession } from "@/lib/auth-client"; import { APIError } from "@/lib/api"; /** 监听 better-auth 会话变为 null(服务端过期/异地登出等场景)*/ function SessionMonitor({ onExpired }: { onExpired: () => void }) { const { data: session, isPending } = useSession(); const wasLoggedIn = useRef(false); useEffect(() => { if (isPending) return; if (session) { wasLoggedIn.current = true; } else if (wasLoggedIn.current) { wasLoggedIn.current = false; onExpired(); } }, [session, isPending, onExpired]); return null; } export function ReactQueryProvider({ children }: { children: React.ReactNode }) { const router = useRouter(); const isHandling = useRef(false); const handleExpired = useCallback(async () => { if (isHandling.current) return; isHandling.current = true; try { await signOut({ fetchOptions: { credentials: "include" } }); } catch { // ignore sign-out errors } toast.warning("登录已过期,请重新登录"); router.push("/login"); }, [router]); // Stable ref so the QueryClient (created once) always calls the latest handler const handleExpiredRef = useRef(handleExpired); handleExpiredRef.current = handleExpired; const [queryClient] = useState( () => new QueryClient({ queryCache: new QueryCache({ onError: (error) => { if (error instanceof APIError && error.status === 401) { handleExpiredRef.current(); } }, }), mutationCache: new MutationCache({ onError: (error) => { if (error instanceof APIError && error.status === 401) { handleExpiredRef.current(); } }, }), defaultOptions: { queries: { staleTime: 10_000, }, }, }), ); return ( {children} ); }