diff --git a/client/src/index.tsx b/client/src/index.tsx index 75e4dcd06..5651386af 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -2,6 +2,7 @@ import "@ant-design/v5-patch-for-react-19"; import React from "react"; import { createRoot } from "react-dom/client"; +import "./utils/authReloadHandler"; import App from "./App"; import "./i18n"; diff --git a/client/src/utils/authReloadHandler.ts b/client/src/utils/authReloadHandler.ts new file mode 100644 index 000000000..0996c0f6a --- /dev/null +++ b/client/src/utils/authReloadHandler.ts @@ -0,0 +1,48 @@ +import { axiosInstance } from "@refinedev/simple-rest"; + +const RELOAD_FLAG_KEY = "spoolmanAuthReloadedAt"; +const RELOAD_COOLDOWN_MS = 30_000; + +/** + * Reloads the page on 401 so a forward-auth proxy can redirect through its + * login portal and back. Cooldown bounds reload loops if recovery fails. The + * PWA service worker's NavigationRoute would otherwise serve the precached + * index.html and prevent the reload from reaching the proxy, so unregister it. + */ +async function reloadOnAuthFailure(): Promise { + let last = 0; + try { + last = Number(localStorage.getItem(RELOAD_FLAG_KEY) || "0"); + } catch { + /* storage unavailable */ + } + if (Date.now() - last < RELOAD_COOLDOWN_MS) return; + try { + localStorage.setItem(RELOAD_FLAG_KEY, String(Date.now())); + } catch { + /* storage unavailable */ + } + if ("serviceWorker" in navigator) { + try { + const registrations = await navigator.serviceWorker.getRegistrations(); + await Promise.all(registrations.map((r) => r.unregister())); + } catch { + /* fall through to reload anyway */ + } + } + window.location.reload(); +} + +axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + if (error?.response?.status === 401) { + // Auto-reload only on idempotent requests so unsaved form data on + // POST/PUT/PATCH/DELETE is preserved — mutation 401s surface through + // the Refine notification provider instead. + const method = String(error.config?.method ?? "get").toLowerCase(); + if (method === "get" || method === "head") void reloadOnAuthFailure(); + } + return Promise.reject(error); + }, +);