1234567891011export function faviconUrl(domain: string): string | null {12 if (!domain) return null;13 if (typeof chrome === "undefined" || !chrome.runtime?.getURL) return null;1415 try {16 const url = new URL(chrome.runtime.getURL("/_favicon/"));17 url.searchParams.set("pageUrl", `https://${domain}`);18 url.searchParams.set("size", "32");19 return url.toString();20 } catch {21 return null;22 }23}24252627function imgSig(img: HTMLImageElement): string {28 const c = document.createElement("canvas");29 c.width = c.height = 8;30 const ctx = c.getContext("2d")!;31 ctx.drawImage(img, 0, 0, 8, 8);32 const d = ctx.getImageData(0, 0, 8, 8).data;33 let s = "";34 for (let i = 0; i < d.length; i += 4) s += `${d[i]},${d[i + 1]},${d[i + 2]};`;35 return s;36}373839let _globeSig: Promise<string | null> | undefined;4041424344454647export function globeFaviconSig(): Promise<string | null> {48 if (_globeSig) return _globeSig;49 _globeSig = new Promise<string | null>((resolve) => {50 const src = faviconUrl("zz-peach-no-favicon-sentinel.nonexistent");51 if (!src) { resolve(null); return; }52 const img = new Image();53 img.onload = () => { try { resolve(imgSig(img)); } catch { resolve(null); } };54 img.onerror = () => resolve(null);55 img.src = src;56 });57 return _globeSig;58}596061626364export function faviconImgSig(img: HTMLImageElement): string | null {65 try { return imgSig(img); } catch { return null; }66}