chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import {
canReview,
currentSession,
grantCurrent,
onSessionEnded,
renewGrant,
signOut as revoke,
} from '#/lib/oauth.js';
/**
* @typedef {Object} SessionContextValue
* @property {import('#/lib/oauth.js').Session|null} session
* @property {(value: import('#/lib/oauth.js').Session|null) => void} setSession
* @property {() => Promise<void>} endSession
* @property {boolean} reviewer - whether the granted scope permits a review
*/
const SessionContext = createContext(
/** @type {SessionContextValue} */ (/** @type {unknown} */ ({})),
);
export function SessionProvider({ children }) {
const [session, setSession] = useState(() => currentSession());
const endSession = useCallback(async () => {
await revoke(session);
setSession(null);
}, [session]);
// A refusal from the server, rather than a sign-out the visitor asked for.
useEffect(() => {
onSessionEnded(() => setSession(null));
}, []);
// The app's scope grows as it does, and a session granted before a growth
// lacks the new piece. Rather than a message, the visitor goes back
// through consent and returns to this page: the consent screen itself is
// the ask, and it names exactly what changed. Never from the callback,
// which is the renewal finishing; once per tab, so a server granting less
// than asked cannot loop the page.
useEffect(() => {
if (!session || grantCurrent(session)) return;
if (window.location.pathname === '/callback') return;
renewGrant(
session,
window.location.pathname + window.location.search + window.location.hash,
);
}, [session]);
return (
<SessionContext.Provider
value={{ session, setSession, endSession, reviewer: canReview(session) }}
>
{children}
</SessionContext.Provider>
);
}
export function useSession() {
return useContext(SessionContext);
}