/** * HTML pages served by the loopback callback server. * * Pattern adapted from pi-mono's `oauth-page.ts` (MIT) — same dark * card style, no third-party assets, escapes user-supplied strings. */ function escapeHtml(value: string): string { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function renderPage(opts: { title: string; heading: string; message: string; details?: string; }): string { const title = escapeHtml(opts.title); const heading = escapeHtml(opts.heading); const message = escapeHtml(opts.message); const details = opts.details ? escapeHtml(opts.details) : undefined; return ` ${title}

${heading}

${message}

${details ? `
${details}
` : ""}
`; } export function oauthSuccessHtml(message: string): string { return renderPage({ title: "Signed in", heading: "Signed in", message, }); } export function oauthErrorHtml(message: string, details?: string): string { return renderPage({ title: "Sign-in failed", heading: "Sign-in failed", message, details, }); }