diff --git a/src/App.tsx b/src/App.tsx index d393f5c..06052df 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import axios from 'axios' import { useState, useCallback } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Plus, LayoutGrid, Table2, ShoppingBag } from 'lucide-react' @@ -13,6 +14,62 @@ import { DeleteConfirm } from './components/DeleteConfirm' import { Pagination } from './components/Pagination' import { ToastContainer, makeToast, type ToastMessage } from './components/Toast' +// The Authentik forward-auth session expires (Android Chrome evicts the PWA +// cookie). When it does, the same-origin /api XHR is 302'd cross-origin to +// Authentik and CORS-blocked, so axios sees no response (or a 401/403). A 5xx +// is a backend problem, not a login problem, and must not trigger the screen. +function isSessionExpired(error: unknown): boolean { + if (!axios.isAxiosError(error)) return false + if (!error.response) return true + return error.response.status === 401 || error.response.status === 403 +} + +function SessionExpiredScreen() { + async function handleSignIn() { + // The PWA service worker serves the precached app shell for every + // navigation, so a plain reload never reaches the network — Authentik's + // forward-auth redirect never fires and re-auth is impossible without + // clearing site data. Do that programmatically: drop the service worker + + // caches, then navigate so the request reaches forward-auth → login. + try { + if ('serviceWorker' in navigator) { + const regs = await navigator.serviceWorker.getRegistrations() + await Promise.all(regs.map(r => r.unregister())) + } + if ('caches' in window) { + const keys = await caches.keys() + await Promise.all(keys.map(k => caches.delete(k))) + } + } catch { + // best-effort — navigate regardless of cleanup failures + } + window.location.href = `${window.location.origin}/?reauth=${Date.now()}` + } + + return ( +
Session expired
++ Your login timed out. Sign in again to continue. +
+