97 lines · 3.2 KB · TypeScript
RawDownload
import { isPublicFaviconHost } from "./hostClassification.ts"; /** * Mirrors Codex's generic Browser Use fallback: ask the page origin for its * conventional favicon and let the image element fall back to a browser glyph. * Chrome-backed tools can pass their tab's explicit favicon URL separately. */function faviconUrlForPage(rawUrl: string | null | undefined, _size = 32): string | null {  if (!rawUrl || rawUrl.length > 4096) return null;  try {    const pageUrl = new URL(rawUrl);    if (pageUrl.protocol !== "http:" && pageUrl.protocol !== "https:") return null;    return new URL("/favicon.ico", pageUrl.origin).href;  } catch {    return null;  }} const THEMED_FAVICON_BY_HOSTNAME: Readonly<  Record<string, Readonly<{ light: string; dark: string }>>> = {  "github.com": {    light: "https://github.githubassets.com/favicons/favicon.svg",    dark: "https://github.githubassets.com/favicons/favicon-dark.svg",  },}; function themedFaviconUrlForPage(  rawUrl: string | null | undefined,  appearance: "light" | "dark",): string | null {  if (!rawUrl || rawUrl.length > 4096) return null;  try {    const pageUrl = new URL(rawUrl);    if (pageUrl.protocol !== "http:" && pageUrl.protocol !== "https:") return null;    return THEMED_FAVICON_BY_HOSTNAME[pageUrl.hostname.toLowerCase()]?.[appearance] ?? null;  } catch {    return null;  }} /** Accepts image URLs supplied by a trusted provider event. */function explicitFaviconUrl(rawUrl: string | null | undefined): string | null {  if (!rawUrl || rawUrl.length > 4096) return null;  try {    const url = new URL(rawUrl);    return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "data:"      ? url.href      : null;  } catch {    return null;  }} /** * Chooses a website icon for the app's resolved theme. Provider-supplied * variants mirror Codex's Chrome-selected favicon path. A small site-owned * fallback table covers websites whose conventional favicon is illegible in * one appearance without recoloring full-color icons. */export function toolActivityFaviconUrl(  icon: {    readonly pageUrl: string;    readonly faviconUrl?: string | undefined;    readonly faviconUrlDark?: string | undefined;  },  appearance: "light" | "dark",  size = 32,): string | null {  if (appearance === "dark") {    return (      explicitFaviconUrl(icon.faviconUrlDark) ??      themedFaviconUrlForPage(icon.pageUrl, "dark") ??      explicitFaviconUrl(icon.faviconUrl) ??      faviconUrlForPage(icon.pageUrl, size)    );  }  return (    explicitFaviconUrl(icon.faviconUrl) ??    themedFaviconUrlForPage(icon.pageUrl, "light") ??    faviconUrlForPage(icon.pageUrl, size)  );} /** Return a public favicon URL without disclosing private or reserved hosts. */export function faviconUrlForOrigin(rawUrl: string | null | undefined, size = 32): string | null {  if (!rawUrl) return null;  try {    const url = new URL(rawUrl);    if (!url.host) return null;    if (url.protocol !== "http:" && url.protocol !== "https:") return null;    if (!isPublicFaviconHost(url.hostname)) return null;    return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(url.host)}&sz=${size}`;  } catch {    return null;  }}