import { Context } from "hono"; import { Env } from "../types"; import { getFeedMetadata } from "../utils/storage"; import { cacheFaviconForDomain, getCachedIcon } from "../utils/favicon-fetcher"; export const FAVICON_PATH = "/favicon.svg"; // Project favicon — reuses the header's envelope logo (brand orange #f6821f), // rendered as a white envelope on a rounded orange square for legibility at 16px. export const FAVICON_SVG = ` `; function projectFavicon(): Response { return new Response(FAVICON_SVG, { headers: { "Content-Type": "image/svg+xml; charset=utf-8", "Cache-Control": "public, max-age=86400", }, }); } export function handle(_c: Context<{ Bindings: Env }>): Response { return projectFavicon(); } /** * Per-feed favicon. Resolves the feed's most recent sender domain and serves * its cached icon; falls back to the project icon for any unresolved case * (no domain, cache miss, or negative cache entry). */ export async function handleFeedFavicon( c: Context<{ Bindings: Env }>, ): Promise { const env = c.env; const feedId = c.req.param("feedId"); if (!feedId) return projectFavicon(); const metadata = await getFeedMetadata(env.EMAIL_STORAGE, feedId); const domain = metadata?.iconDomain; if (!domain) return projectFavicon(); const icon = await getCachedIcon(domain, env); if (icon) { return new Response(icon.bytes, { headers: { "Content-Type": icon.contentType, "Cache-Control": "public, max-age=86400", }, }); } // Known domain but nothing cached yet: warm the cache in the background and // serve the fallback for now. try { c.executionCtx.waitUntil(cacheFaviconForDomain(domain, env)); } catch { // No ExecutionContext (e.g. tests) — fallback is served regardless. } return projectFavicon(); }