import { trpc } from "@/lib/trpc"; import { UNAUTHED_ERR_MSG } from '@shared/const'; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink, httpLink, splitLink, TRPCClientError } from "@trpc/client"; import { createRoot } from "react-dom/client"; import superjson from "superjson"; import App from "./App"; import { getLoginUrl } from "./const"; import "./index.css"; // 禁用 HMR 失败时的自动页面刷新,保留热更新功能 if (import.meta.hot) { // 阻止 HMR 失败时的完整页面刷新 import.meta.hot.on('vite:beforeFullReload', (payload) => { console.log('[HMR] Preventing full reload, change detected:', payload); // 返回 false 阻止刷新(但这个钩子实际上不能阻止,只是通知) }); // 当 HMR 更新失败时,不刷新页面,只在控制台显示警告 import.meta.hot.on('vite:error', (err) => { console.warn('[HMR] Update failed, manual refresh may be needed:', err); }); // 接受所有模块更新,防止因某些模块不支持 HMR 而触发刷新 import.meta.hot.accept(() => { console.log('[HMR] Module updated'); }); } const queryClient = new QueryClient(); const redirectToLoginIfUnauthorized = (error: unknown) => { if (!(error instanceof TRPCClientError)) return; if (typeof window === "undefined") return; const isUnauthorized = error.message === UNAUTHED_ERR_MSG; if (!isUnauthorized) return; window.location.href = getLoginUrl(); }; queryClient.getQueryCache().subscribe(event => { if (event.type === "updated" && event.action.type === "error") { const error = event.query.state.error; redirectToLoginIfUnauthorized(error); console.error("[API Query Error]", error); } }); queryClient.getMutationCache().subscribe(event => { if (event.type === "updated" && event.action.type === "error") { const error = event.mutation.state.error; redirectToLoginIfUnauthorized(error); console.error("[API Mutation Error]", error); } }); const trpcLinkOptions = { url: "/api/trpc", transformer: superjson, fetch(input: RequestInfo | URL, init?: RequestInit) { // 给每个请求加时间戳,防止 NPS / 反向代理缓存 GET 响应 let url: string; if (input instanceof URL) { input.searchParams.set('_t', Date.now().toString()); url = input.toString(); } else if (typeof input === 'string') { const sep = input.includes('?') ? '&' : '?'; url = `${input}${sep}_t=${Date.now()}`; } else { url = input as string; } return globalThis.fetch(url, { ...(init ?? {}), credentials: "include", }); }, } as const; const trpcClient = trpc.createClient({ links: [ splitLink({ condition(op) { return op.type === "mutation"; }, true: httpLink(trpcLinkOptions), false: httpBatchLink(trpcLinkOptions), }), ], }); createRoot(document.getElementById("root")!).render( );