import { trpc } from "@/lib/trpc"; import { UNAUTHED_ERR_MSG } from '@shared/const'; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink, 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"; const queryClient = new QueryClient(); const ASSET_REFRESH_KEY = "asset-recovery-reloaded"; 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(); }; function reloadForStaleAsset(reason: string) { if (typeof window === "undefined") return; const alreadyReloaded = window.sessionStorage.getItem(ASSET_REFRESH_KEY) === "1"; if (alreadyReloaded) { console.error("[Asset Recovery] stale asset still failing after reload", reason); return; } window.sessionStorage.setItem(ASSET_REFRESH_KEY, "1"); console.warn("[Asset Recovery] reloading page due to stale asset failure:", reason); window.location.reload(); } function clearAssetRecoveryFlag() { if (typeof window === "undefined") return; window.sessionStorage.removeItem(ASSET_REFRESH_KEY); } if (typeof window !== "undefined") { window.addEventListener("load", () => { clearAssetRecoveryFlag(); }, { once: true }); window.addEventListener("vite:preloadError", (event) => { const customEvent = event as Event & { payload?: unknown; preventDefault: () => void }; customEvent.preventDefault(); reloadForStaleAsset(String(customEvent.payload ?? "vite preload error")); }); window.addEventListener("error", (event) => { const target = event.target; if (!(target instanceof HTMLLinkElement || target instanceof HTMLScriptElement)) { return; } const assetUrl = target instanceof HTMLLinkElement ? target.href : target.src; if (assetUrl.includes("/assets/")) { reloadForStaleAsset(assetUrl); } }, true); window.addEventListener("unhandledrejection", (event) => { const reason = event.reason instanceof Error ? event.reason.message : String(event.reason ?? ""); if ( reason.includes("Failed to fetch dynamically imported module") || reason.includes("Importing a module script failed") || reason.includes("Unable to preload CSS") ) { reloadForStaleAsset(reason); } }); } 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 trpcClient = trpc.createClient({ links: [ httpBatchLink({ url: "/api/trpc", transformer: superjson, fetch(input, init) { return globalThis.fetch(input, { ...(init ?? {}), credentials: "include", }); }, }), ], }); const analyticsEndpoint = import.meta.env.VITE_ANALYTICS_ENDPOINT; const analyticsWebsiteId = import.meta.env.VITE_ANALYTICS_WEBSITE_ID; if (analyticsEndpoint && analyticsWebsiteId && typeof document !== "undefined") { const script = document.createElement("script"); script.defer = true; script.src = `${analyticsEndpoint.replace(/\/$/, "")}/umami`; script.dataset.websiteId = analyticsWebsiteId; document.head.appendChild(script); } createRoot(document.getElementById("root")!).render( );