128 行
4.0 KiB
TypeScript
128 行
4.0 KiB
TypeScript
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(
|
|
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
|
<QueryClientProvider client={queryClient}>
|
|
<App />
|
|
</QueryClientProvider>
|
|
</trpc.Provider>
|
|
);
|