/** * Renders the page another gerbeur instance sends a user back to once they * let it sign them in here, `/federation/callback`. * * @module */ import { useEffect, useRef, useState } from "react"; import { Link, useNavigate, useSearchParams } from "react-router"; import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { API_URL } from "../config/api.ts"; import type { Wire } from "../../shared/api.ts"; import type { FederationSessionRequest } from "../../shared/federation.ts"; import type { AuthResponse } from "../../shared/user.ts"; import { useAuth } from "../hooks/auth/useAuth.ts"; import { forgetSignIn, startedHere } from "../utils/auth/federation.ts"; import { useDocumentTitle } from "../hooks/shared/useDocumentTitle.ts"; import { PageShell } from "../components/layout/PageShell.tsx"; import { ErrorCard } from "../components/shared/ErrorCard.tsx"; import { expectOk, friendlyFetchError } from "../utils/shared/api.ts"; import { deserializeAuthResponse } from "../utils/shared/deserialize.ts"; /** * Renders `/federation/callback?code=&state=`. It finishes the sign-in * (`POST /api/federation/sessions`) if this tab started it (`startedHere`), * and once: a code works only once, and a second request would fail the * sign-in. On success the user lands on `/`, signed in. */ export function FederationCallback(): React.JSX.Element { useDocumentTitle(t`Log in`); const [params] = useSearchParams(); const navigate = useNavigate(); const { login } = useAuth(); const code = params.get("code"); const state = params.get("state"); const [error, setError] = useState(() => !code || !state ? t`This sign-in link is incomplete.` : !startedHere(state) ? t`This sign-in was started elsewhere. Start again from the login page.` : null ); const sent = useRef(false); useEffect(() => { if (sent.current || error || !code || !state) return; sent.current = true; forgetSignIn(); fetch(`${API_URL}/api/federation/sessions`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code, state } satisfies FederationSessionRequest), }) .then((res) => expectOk>(res)) .then((data) => { login(deserializeAuthResponse(data)); navigate("/", { replace: true }); }) .catch((err) => setError(friendlyFetchError(err))); }, [code, state, error, login, navigate]); return ( {error ? (

Back to the login page

) : (

Signing you in…

)}
); }