// --------------------------------------------------------------------------- // @executor/plugin-oauth2/http — server-side HTTP helpers for OAuth popups. // // `popupDocument` renders the HTML page returned by the OAuth redirect // handler. The page immediately `postMessage`s the result back to the // opener window and falls back to a `BroadcastChannel` if the opener is // gone (mobile Safari closes the opener on popup open in some cases). // // `runOAuthCallback` wraps the "call completeOAuth, turn Exit into // popup payload, render HTML" glue so plugin handlers stay one-liners. // --------------------------------------------------------------------------- import { Cause, Effect } from "effect"; import { OAUTH_POPUP_MESSAGE_TYPE, type OAuthPopupResult, } from "./popup-types"; export { OAUTH_POPUP_MESSAGE_TYPE, isOAuthPopupResult } from "./popup-types"; export type { OAuthPopupResult } from "./popup-types"; // --------------------------------------------------------------------------- // HTML generation // --------------------------------------------------------------------------- const escapeHtml = (value: string): string => value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); /** * Serialize for embedding inside a ` `; }; // --------------------------------------------------------------------------- // Callback wrapper — turns a completeOAuth Effect into a popup HTML string. // --------------------------------------------------------------------------- export type OAuthCallbackUrlParams = { readonly state: string; readonly code?: string | null; readonly error?: string | null; readonly error_description?: string | null; }; export type RunOAuthCallbackInput = { /** The plugin's `completeOAuth` — resolves to the auth descriptor on success. */ readonly complete: (params: { readonly state: string; readonly code: string | null; readonly error: string | null; }) => Effect.Effect; readonly urlParams: OAuthCallbackUrlParams; /** Map a plugin-specific error into a user-facing message. */ readonly toErrorMessage: (error: unknown) => string; readonly channelName: string; }; /** * Run a plugin's `completeOAuth` against URL params from the OAuth redirect, * wrap the success / failure in an `OAuthPopupResult`, and return the HTML * body ready to hand to `HttpServerResponse.html(...)`. * * This never fails — errors become a `{ ok: false }` result so the popup * can still render and close itself. */ export const runOAuthCallback = ( input: RunOAuthCallbackInput, ): Effect.Effect => input .complete({ state: input.urlParams.state, code: input.urlParams.code ?? null, error: input.urlParams.error ?? input.urlParams.error_description ?? null, }) .pipe( Effect.map( (auth): OAuthPopupResult => ({ type: OAUTH_POPUP_MESSAGE_TYPE, ok: true, sessionId: input.urlParams.state, ...auth, }), ), Effect.catchAllCause((cause) => Effect.succeed>({ type: OAUTH_POPUP_MESSAGE_TYPE, ok: false, sessionId: null, error: input.toErrorMessage(Cause.squash(cause)), }), ), Effect.map((result) => popupDocument(result, input.channelName)), );