mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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 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 (
 | 
			
		||||
        <SWRProvider isUnauthorized={!isLoggedIn}>
 | 
			
		||||
        <SWRProvider>
 | 
			
		||||
            <Suspense fallback={<Loader />}>
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={!hasFetchedAuth}
 | 
			
		||||
 | 
			
		||||
@ -1,78 +1,18 @@
 | 
			
		||||
import { mutate, SWRConfig, useSWRConfig } from 'swr';
 | 
			
		||||
import { useNavigate } from 'react-router';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { formatApiPath } from 'utils/formatPath';
 | 
			
		||||
import { SWRConfig } from 'swr';
 | 
			
		||||
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 {
 | 
			
		||||
    isUnauthorized: boolean;
 | 
			
		||||
}
 | 
			
		||||
export const SWRProvider: React.FC = ({ children }) => {
 | 
			
		||||
    const { refetchUser } = useAuthUser();
 | 
			
		||||
 | 
			
		||||
const INVALID_TOKEN_ERROR = 'InvalidTokenError';
 | 
			
		||||
 | 
			
		||||
const SWRProvider: React.FC<ISWRProviderProps> = ({
 | 
			
		||||
    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 (
 | 
			
		||||
        <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>
 | 
			
		||||
    );
 | 
			
		||||
    return <SWRConfig value={{ onError }}>{children}</SWRConfig>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SWRProvider;
 | 
			
		||||
 | 
			
		||||
@ -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<unknown> => {
 | 
			
		||||
    try {
 | 
			
		||||
        return await res.json();
 | 
			
		||||
    } catch {
 | 
			
		||||
        return res.statusText;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 = {
 | 
			
		||||
    Accept: 'application/json',
 | 
			
		||||
    'Content-Type': 'application/json',
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user