80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
"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 (
|
||
<QueryClientProvider client={queryClient}>
|
||
<SessionMonitor onExpired={handleExpired} />
|
||
{children}
|
||
</QueryClientProvider>
|
||
);
|
||
}
|