import { Ref, useCallback, useEffect, useRef, } from 'react'; import { consumeRedirectUrl, isSafeAppUrl, setRedirectUrl, } from './RedirectManager'; const REDIRECT_PARAM: string = 'redirect'; /** * Append a redirect param to the given login URL. * @param loginUrl * @param targetUrl * @param paramName */ export function appendRedirectParam( loginUrl: string, targetUrl: string, paramName: string = REDIRECT_PARAM, ): string { const u: URL = new URL(loginUrl, window.location.origin); u.searchParams.set(paramName, targetUrl); return u.pathname + u.search + u.hash; // always relative } type RequireAccessOpts = { hasAccess: boolean, currentUrl: string, // the router provides this fallbackUrl: string, navigate: (url: string) => void, // the router's navigate/push function useQueryParam?: boolean, paramName?: string, shouldRedirect?: boolean, // skipping redirect on login page to avoid loop }; /** * A hook to require access to a page, otherwise redirect to a fallback URL (e.g. login page). * @param hasAccess * @param currentUrl * @param loginUrl * @param navigate * @param useQueryParam * @param paramName */ export function useRequireAccess( { hasAccess, currentUrl, fallbackUrl, navigate, useQueryParam = true, paramName = REDIRECT_PARAM, shouldRedirect = true, }: Readonly, ) { useEffect(() => { if (!hasAccess && shouldRedirect) { setRedirectUrl(currentUrl); const next: string = useQueryParam ? appendRedirectParam(fallbackUrl, currentUrl, paramName) : fallbackUrl; navigate(next); } }, [hasAccess, currentUrl, fallbackUrl, navigate, useQueryParam, paramName, shouldRedirect]); return { allowed: hasAccess }; } type ReturnAfterAccessOpts = { hasAccess: boolean, navigate: (url: string) => void, defaultRedirect?: string, paramName?: string, }; /** * A hook to redirect the user after login/access to the intended page. * @param hasAccess * @param navigate * @param defaultRedirect * @param paramName */ export function useReturnAfterAccess( { hasAccess, navigate, defaultRedirect = '/', paramName = REDIRECT_PARAM, }: Readonly, ): void { const redirectRef: Ref = useRef(null); const returnToRedirection: () => void = useCallback(() => { if (redirectRef.current === null) { // 1. check session storage let target: string | null = consumeRedirectUrl(); // 2. fallback to query param if (!target) { const qp: string | null = new URLSearchParams(window.location.search).get(paramName!); if (qp) { target = qp; } } // 3. validate or fallback if (!isSafeAppUrl(target)) { target = defaultRedirect!; } redirectRef.current = target; } navigate(`${redirectRef.current ?? defaultRedirect}`); }, [defaultRedirect, navigate, paramName]); useEffect(() => { if (hasAccess) { returnToRedirection(); } }, [hasAccess, returnToRedirection]); }