import { createSignal, Show } from "solid-js"; import { useNavigate } from "@solidjs/router"; import { OAuth2, createClient } from "matrix-js-sdk"; import { adoptMatrixClient, setActiveAccountId, upsertSession } from "../../matrix"; // The stored context produced during login (shape matches OAuth2's Context) interface StoredContext { clientId: string; deviceId?: string; codeVerifier?: string; redirectUri: string; } interface MappedClient { homeserverUrl: string; context: StoredContext; } function parseCallbackParams(): URLSearchParams { // response_mode is "query", but handle "fragment" too since the spec says // the IdP may answer in either fragment or query depending on the request. const query = new URLSearchParams(window.location.search); const hashValue = window.location.hash.slice(1); if (!query.get("state") && hashValue.includes("=")) { return new URLSearchParams(hashValue); } return query; } function mapClient(state: string): MappedClient | null { const stored = sessionStorage.getItem(`asterisk.oauth.${state}`); if (!stored) return null; sessionStorage.removeItem(`asterisk.oauth.${state}`); return JSON.parse(stored) as MappedClient; } export default function AuthCallback() { const navigate = useNavigate(); const [error, setError] = createSignal(null); const [errorUri, setErrorUri] = createSignal(null); // Component body runs once on mount in Solid v2 — no onMount needed. void (async () => { const params = parseCallbackParams(); // Error case: the authorisation failed, params carry error info. const errorParam = params.get("error"); if (errorParam) { setError( params.get("error_description") ? `${errorParam}: ${params.get("error_description")}` : errorParam, ); setErrorUri(params.get("error_uri")); return; } const code = params.get("code"); const state = params.get("state"); if (!code || !state) { setError("Missing code or state in callback URL."); return; } const mapped = mapClient(state); if (!mapped) { setError("No matching authorisation request found for this state."); return; } const { homeserverUrl, context } = mapped; const oauth = new OAuth2( await createClient({ baseUrl: homeserverUrl }).getAuthMetadata(), context, ); try { // Exchange the code for tokens at the token endpoint. const token = await oauth.completeAuthorizationCodeGrant(code); const clientOpts = { baseUrl: homeserverUrl, deviceId: context.deviceId, accessToken: token.access_token, refreshToken: token.refresh_token, }; // whoami validates the token and resolves the user id so that the // resulting client's getUserId() is non-null for adoption. const who = await createClient(clientOpts).whoami(); const session = adoptMatrixClient( createClient({ ...clientOpts, userId: who.user_id }), ); // clientId is needed for refresh-token grants; it lives only in the // SDK's OAuth context, so we have to patch it into the session here. upsertSession({ ...session, clientId: context.clientId }); setActiveAccountId(session.id); navigate("/"); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } })(); return ( Completing sign in…

} > {(getError) => (

Sign in failed

{getError()}

{(getUri) => (

More information about this error

)}

Back to login

)}
); }