Public Access
35 lines
1.5 KiB
React
35 lines
1.5 KiB
React
import {useEffect, useRef} from 'react';
|
|
import {Navigate, Outlet, useLocation} from 'react-router-dom';
|
|
import useSessionExpired from '../hooks/useSessionExpired.js';
|
|
import {sessionEvents} from '../utils/api.js';
|
|
import {hasSession} from '../utils/auth.js';
|
|
|
|
/**
|
|
* Layout route of the pages that need a login. Without a session it opens the login page, which comes back here;
|
|
* a 401 on one of these pages ends the session (see apiFetch).
|
|
*/
|
|
export default function RequireSession() {
|
|
const {pathname, search} = useLocation();
|
|
const expireSession = useSessionExpired();
|
|
// In a ref, so the listener is added once and not again on every navigation.
|
|
const expireRef = useRef(expireSession);
|
|
expireRef.current = expireSession;
|
|
// A render before the login page shows must not redirect again without the "session expired" state.
|
|
const expiring = useRef(false);
|
|
|
|
useEffect(() => {
|
|
// Parallel requests each fire; only the first ends the session. Another tab may have removed the flag already.
|
|
const onExpired = () => {
|
|
if (expiring.current) return;
|
|
expiring.current = true;
|
|
expireRef.current();
|
|
};
|
|
sessionEvents.addEventListener('expired', onExpired);
|
|
return () => sessionEvents.removeEventListener('expired', onExpired);
|
|
}, []);
|
|
|
|
return hasSession() || expiring.current
|
|
? <Outlet/>
|
|
: <Navigate to="/login" replace state={{from: pathname + search}}/>;
|
|
}
|