mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
refactor: simplify login redirect logic (#1987)
This commit is contained in:
parent
23e43b9114
commit
ae3d6c06cf
@ -5,7 +5,7 @@ import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker';
|
|||||||
import Loader from 'component/common/Loader/Loader';
|
import Loader from 'component/common/Loader/Loader';
|
||||||
import NotFound from 'component/common/NotFound/NotFound';
|
import NotFound from 'component/common/NotFound/NotFound';
|
||||||
import { ProtectedRoute } from 'component/common/ProtectedRoute/ProtectedRoute';
|
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 ToastRenderer from 'component/common/ToastRenderer/ToastRenderer';
|
||||||
import { routes } from 'component/menu/routes';
|
import { routes } from 'component/menu/routes';
|
||||||
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
|
||||||
@ -21,7 +21,6 @@ export const App = () => {
|
|||||||
const { authDetails } = useAuthDetails();
|
const { authDetails } = useAuthDetails();
|
||||||
const { user } = useAuthUser();
|
const { user } = useAuthUser();
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
const isLoggedIn = Boolean(user?.id);
|
|
||||||
const hasFetchedAuth = Boolean(authDetails || user);
|
const hasFetchedAuth = Boolean(authDetails || user);
|
||||||
usePlausibleTracker();
|
usePlausibleTracker();
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ export const App = () => {
|
|||||||
: routes;
|
: routes;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SWRProvider isUnauthorized={!isLoggedIn}>
|
<SWRProvider>
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!hasFetchedAuth}
|
condition={!hasFetchedAuth}
|
||||||
|
@ -1,78 +1,18 @@
|
|||||||
import { mutate, SWRConfig, useSWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
import { useNavigate } from 'react-router';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { USER_ENDPOINT_PATH } from 'hooks/api/getters/useAuth/useAuthEndpoint';
|
import { ResponseError } from 'utils/apiUtils';
|
||||||
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
|
|
||||||
interface ISWRProviderProps {
|
export const SWRProvider: React.FC = ({ children }) => {
|
||||||
isUnauthorized: boolean;
|
const { refetchUser } = useAuthUser();
|
||||||
}
|
|
||||||
|
|
||||||
const INVALID_TOKEN_ERROR = 'InvalidTokenError';
|
const onError = (error: Error) => {
|
||||||
|
if (error instanceof ResponseError && error.status === 401) {
|
||||||
const SWRProvider: React.FC<ISWRProviderProps> = ({
|
// Refetch the user's data if they appear to be logged out.
|
||||||
children,
|
// This may trigger a login page redirect in ProtectedRoute.
|
||||||
isUnauthorized,
|
refetchUser();
|
||||||
}) => {
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <SWRConfig value={{ onError }}>{children}</SWRConfig>;
|
||||||
<SWRConfig
|
|
||||||
value={{
|
|
||||||
onErrorRetry: (
|
|
||||||
error,
|
|
||||||
_key,
|
|
||||||
_config,
|
|
||||||
revalidate,
|
|
||||||
{ retryCount }
|
|
||||||
) => {
|
|
||||||
// Never retry on 404 or 401.
|
|
||||||
if (error.status < 499) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => revalidate({ retryCount }), 5000);
|
|
||||||
},
|
|
||||||
onError: handleFetchError,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</SWRConfig>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SWRProvider;
|
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
|
import { ResponseError } from 'utils/apiUtils';
|
||||||
|
|
||||||
const handleErrorResponses = (target: string) => async (res: Response) => {
|
const handleErrorResponses = (target: string) => async (res: Response) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const error = new Error(
|
throw new ResponseError(
|
||||||
`An error occurred while trying to get ${target}`
|
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;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseErrorResponse = async (res: Response): Promise<unknown> => {
|
||||||
|
try {
|
||||||
|
return await res.json();
|
||||||
|
} catch {
|
||||||
|
return res.statusText;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default handleErrorResponses;
|
export default handleErrorResponses;
|
||||||
|
@ -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 = {
|
export const headers = {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Loading…
Reference in New Issue
Block a user