Public Access
refactor(session): one session store for login, logout and 401
This commit is contained in:
1 parent
2a22789435
commit
cbafa3d540
23 files changed
+246
-280
No files matched your search
@@ -1,17 +1,16 @@
|
||||
import {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {request} from '../utils/api.js';
|
||||
import {SESSION_KEY, hasSession} from '../utils/auth.js';
|
||||
import {hasSession} from '../utils/auth.js';
|
||||
import useSession from '../hooks/useSession.js';
|
||||
import {ME_URL, REQUEST_TIMEOUT_MS} from '../utils/settings.js';
|
||||
|
||||
const UserContext = createContext(null);
|
||||
// Apart from the user, so a component that only reloads or clears it does not render again on each change.
|
||||
const ActionsContext = createContext(null);
|
||||
const CurrentUserContext = createContext(null);
|
||||
|
||||
/**
|
||||
* GET /api/users/me for the whole app. `user` is undefined until the first answer and null without a session.
|
||||
* `reload()` loads it again and resolves to the `request()` result (not ok for a 200 without a body).
|
||||
* `updateUser(fn)` changes it locally after a save, and skips it when the user is gone by then; `setUser(null)`
|
||||
* drops it. A save that replaces settings still loads /me itself (see saveSettings), as it must be fresh.
|
||||
* `updateUser(fn)` changes it locally after a save, and skips it when the user is gone by then. It follows the
|
||||
* session. A save that replaces settings still loads /me itself (see saveSettings), as it must be fresh.
|
||||
*/
|
||||
export function CurrentUserProvider({children}) {
|
||||
const [user, setUser] = useState(() => (hasSession() ? undefined : null));
|
||||
@@ -32,43 +31,29 @@ export function CurrentUserProvider({children}) {
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
// A new stamp is a login, a "view as" or a settings save, also in another tab.
|
||||
const {stamp} = useSession();
|
||||
useEffect(() => {
|
||||
if (hasSession()) reload();
|
||||
// A login, logout or "view as" in another tab.
|
||||
const onStorage = (event) => {
|
||||
if (event.key !== null && event.key !== SESSION_KEY) return;
|
||||
if (hasSession()) reload();
|
||||
else setUser(null);
|
||||
};
|
||||
window.addEventListener('storage', onStorage);
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
}, [reload]);
|
||||
if (stamp) {
|
||||
reload();
|
||||
} else {
|
||||
// A /me answer still on the way must not bring the user back.
|
||||
latest.current += 1;
|
||||
setUser(null);
|
||||
}
|
||||
}, [stamp, reload]);
|
||||
|
||||
const updateUser = useCallback((change) => setUser((current) => current && change(current)), []);
|
||||
const actions = useMemo(() => ({reload, setUser, updateUser}), [reload, updateUser]);
|
||||
const value = useMemo(() => ({user, reload, updateUser}), [user, reload, updateUser]);
|
||||
|
||||
return (
|
||||
<ActionsContext.Provider value={actions}>
|
||||
<UserContext.Provider value={user}>{children}</UserContext.Provider>
|
||||
</ActionsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const NO_ACTIONS = {reload: async () => ({ok: false, status: 0, data: null}), setUser: () => {}, updateUser: () => {}};
|
||||
|
||||
/**
|
||||
* `{reload, setUser, updateUser}`, without the user. Outside a provider (e.g. a test of one component) they do
|
||||
* nothing.
|
||||
*/
|
||||
export function useCurrentUserActions() {
|
||||
return useContext(ActionsContext) ?? NO_ACTIONS;
|
||||
return <CurrentUserContext.Provider value={value}>{children}</CurrentUserContext.Provider>;
|
||||
}
|
||||
|
||||
/**
|
||||
* `{user, reload, setUser, updateUser}`; outside a provider there is no user.
|
||||
* `{user, reload, updateUser}`.
|
||||
*/
|
||||
export function useCurrentUserContext() {
|
||||
const user = useContext(UserContext);
|
||||
const actions = useContext(ActionsContext);
|
||||
return {user: actions ? user : null, ...(actions ?? NO_ACTIONS)};
|
||||
const value = useContext(CurrentUserContext);
|
||||
if (!value) throw new Error('useCurrentUserContext must be used inside <CurrentUserProvider>');
|
||||
return value;
|
||||
}
|
||||
Reference in new issue
Block a user