"use client"; import { useEffect } from "react"; const SEEN = new Set(); const MAX_SEEN = 50; function fingerprint(type: string, message: string, stack?: string): string { const head = (stack || "").split("\n").slice(0, 3).join("\n"); return `${type}|${message.slice(0, 80)}|${head.slice(0, 120)}`; } async function report(payload: { type: string; message: string; stack?: string; url?: string; }) { const fp = fingerprint(payload.type, payload.message, payload.stack); if (SEEN.has(fp)) return; SEEN.add(fp); if (SEEN.size > MAX_SEEN) { const first = SEEN.values().next().value; if (first) SEEN.delete(first); } try { await fetch("/api/errors", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...payload, url: payload.url || location.href }), keepalive: true, }); } catch {} } export function ErrorReporter() { useEffect(() => { if (typeof window === "undefined") return; if (process.env.NODE_ENV !== "production") return; const onError = (e: ErrorEvent) => { report({ type: "window.error", message: e.message || String(e.error), stack: e.error?.stack, url: e.filename, }); }; const onRejection = (e: PromiseRejectionEvent) => { const reason = e.reason; const message = reason instanceof Error ? reason.message : String(reason); const stack = reason instanceof Error ? reason.stack : undefined; report({ type: "unhandledrejection", message, stack }); }; window.addEventListener("error", onError); window.addEventListener("unhandledrejection", onRejection); return () => { window.removeEventListener("error", onError); window.removeEventListener("unhandledrejection", onRejection); }; }, []); return null; }