~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
68 lines · 1.9 KB Raw History
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);
}