import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { NextResponse } from "next/server"; import { getSiteSettings } from "@/lib/db/queries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export const revalidate = 0; const DEFAULT_CONTENT_TYPE = "image/x-icon"; const FETCH_TIMEOUT_MS = 10_000; function noStoreHeaders(contentType: string) { return { "Content-Type": contentType, "Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate", Pragma: "no-cache", Expires: "0", }; } function inferContentType(url: string) { const lower = url.toLowerCase(); if (lower.endsWith(".png")) return "image/png"; if (lower.endsWith(".svg")) return "image/svg+xml"; if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; if (lower.endsWith(".webp")) return "image/webp"; return DEFAULT_CONTENT_TYPE; } function resolveFaviconUrl(rawUrl: string, requestUrl: string) { const input = rawUrl.trim(); if (!input) { return null; } try { return new URL(input).toString(); } catch { // ignore and try resolving relative path } const authUrl = process.env.AUTH_URL?.trim(); if (authUrl) { try { return new URL(input, authUrl).toString(); } catch { // ignore and fallback to request origin } } try { return new URL(input, requestUrl).toString(); } catch { return null; } } async function readLocalFallbackFavicon() { const file = await readFile(join(process.cwd(), "public", "default-favicon.ico")); return new NextResponse(file, { status: 200, headers: noStoreHeaders(DEFAULT_CONTENT_TYPE), }); } export async function GET(request: Request) { try { const settings = await getSiteSettings(); const rawFaviconUrl = String(settings?.favicon || "").trim(); if (!rawFaviconUrl) { return await readLocalFallbackFavicon(); } const faviconUrl = resolveFaviconUrl(rawFaviconUrl, request.url); if (!faviconUrl) { console.error("[favicon] Invalid favicon URL:", rawFaviconUrl); return await readLocalFallbackFavicon(); } const targetUrl = `${faviconUrl}${faviconUrl.includes("?") ? "&" : "?"}v=${Date.now()}`; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); let upstream: Response; try { upstream = await fetch(targetUrl, { cache: "no-store", signal: controller.signal, redirect: "follow", headers: { Accept: "image/*,*/*;q=0.8", "User-Agent": "Mozilla/5.0 (compatible; BuyiAIFavicon/1.0)", }, }); } finally { clearTimeout(timeout); } if (!upstream.ok) { console.error("[favicon] Upstream request failed:", { targetUrl, status: upstream.status, statusText: upstream.statusText, }); return await readLocalFallbackFavicon(); } const buffer = await upstream.arrayBuffer(); const contentType = upstream.headers.get("content-type") || inferContentType(faviconUrl); return new NextResponse(buffer, { status: 200, headers: noStoreHeaders(contentType), }); } catch (error) { console.error("[favicon] Failed to proxy favicon:", error); return await readLocalFallbackFavicon(); } }