diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx index 62c4d55233..a3706268bf 100644 --- a/frontend/src/component/App.tsx +++ b/frontend/src/component/App.tsx @@ -5,7 +5,7 @@ import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker'; import Loader from 'component/common/Loader/Loader'; import NotFound from 'component/common/NotFound/NotFound'; import { ProtectedRoute } from 'component/common/ProtectedRoute/ProtectedRoute'; -import SWRProvider from 'component/providers/SWRProvider/SWRProvider'; +import { SWRProvider } from 'component/providers/SWRProvider/SWRProvider'; import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer'; import { routes } from 'component/menu/routes'; import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails'; @@ -21,7 +21,6 @@ export const App = () => { const { authDetails } = useAuthDetails(); const { user } = useAuthUser(); const { isOss } = useUiConfig(); - const isLoggedIn = Boolean(user?.id); const hasFetchedAuth = Boolean(authDetails || user); usePlausibleTracker(); @@ -30,7 +29,7 @@ export const App = () => { : routes; return ( - + }> { + const { refetchUser } = useAuthUser(); -const INVALID_TOKEN_ERROR = 'InvalidTokenError'; - -const SWRProvider: React.FC = ({ - children, - isUnauthorized, -}) => { - const { cache } = useSWRConfig(); - const navigate = useNavigate(); - const { setToastApiError } = useToast(); - - // @ts-expect-error - const handleFetchError = error => { - if (error.status === 401) { - const path = location.pathname; - // Only populate user with authDetails if 401 and - // error is not invalid token - if (error?.info?.name !== INVALID_TOKEN_ERROR) { - mutate(USER_ENDPOINT_PATH, { ...error.info }, false); - } - - if ( - path === formatApiPath('login') || - path === formatApiPath('new-user') || - path === formatApiPath('reset-password') || - path === formatApiPath('forgotten-password') - ) { - return; - } - - // @ts-expect-error - cache.clear(); - - navigate('/login'); - return; - } - - if (!isUnauthorized) { - setToastApiError(error.message); + const onError = (error: Error) => { + if (error instanceof ResponseError && error.status === 401) { + // Refetch the user's data if they appear to be logged out. + // This may trigger a login page redirect in ProtectedRoute. + refetchUser(); } }; - return ( - { - // Never retry on 404 or 401. - if (error.status < 499) { - return error; - } - - setTimeout(() => revalidate({ retryCount }), 5000); - }, - onError: handleFetchError, - }} - > - {children} - - ); + return {children}; }; - -export default SWRProvider; diff --git a/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts b/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts index ac51696510..23d7fd94cb 100644 --- a/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts +++ b/frontend/src/hooks/api/getters/httpErrorResponseHandler.ts @@ -1,24 +1,23 @@ +import { ResponseError } from 'utils/apiUtils'; + const handleErrorResponses = (target: string) => async (res: Response) => { if (!res.ok) { - const error = new Error( - `An error occurred while trying to get ${target}` + throw new ResponseError( + target, + res.status, + await parseErrorResponse(res) ); - // Try to resolve body, but don't rethrow res.json is not a function - try { - // @ts-expect-error - error.info = await res.json(); - } catch (e) { - // @ts-expect-error - error.info = {}; - } - // @ts-expect-error - error.status = res.status; - // @ts-expect-error - error.statusText = res.statusText; - throw error; } return res; }; +const parseErrorResponse = async (res: Response): Promise => { + try { + return await res.json(); + } catch { + return res.statusText; + } +}; + export default handleErrorResponses; diff --git a/frontend/src/utils/apiUtils.ts b/frontend/src/utils/apiUtils.ts index c0c7c8321d..1eaf0e3cc0 100644 --- a/frontend/src/utils/apiUtils.ts +++ b/frontend/src/utils/apiUtils.ts @@ -57,6 +57,18 @@ export class NotFoundError extends Error { } } +export class ResponseError extends Error { + status: number; + body: unknown; + + constructor(target: string, status: number, body: unknown) { + super(`An error occurred while trying to get ${target}.`); + this.name = 'ResponseError'; + this.status = status; + this.body = body; + } +} + export const headers = { Accept: 'application/json', 'Content-Type': 'application/json',