diff --git a/apps/project/src/components/Turnstile.tsx b/apps/project/src/components/Turnstile.tsx index 809baa4d..e075f860 100644 --- a/apps/project/src/components/Turnstile.tsx +++ b/apps/project/src/components/Turnstile.tsx @@ -42,6 +42,9 @@ function loadTurnstileScript(): Promise { script.src = SCRIPT_SRC; script.async = true; script.defer = true; + // Report a real Turnstile load failure with a usable stack instead of an + // opaque cross-origin "Script error.". + script.crossOrigin = "anonymous"; script.onload = () => resolve(); script.onerror = () => reject(new Error("Failed to load Turnstile")); document.head.appendChild(script); diff --git a/packages/observability/src/initAnalytics.ts b/packages/observability/src/initAnalytics.ts index a15f5525..8db43738 100644 --- a/packages/observability/src/initAnalytics.ts +++ b/packages/observability/src/initAnalytics.ts @@ -1,5 +1,5 @@ import { appData } from "@repo/lib"; -import posthog from "posthog-js"; +import posthog, { type CaptureResult } from "posthog-js"; export type AnalyticsSurface = "project" | "remote" | "renderer"; @@ -14,6 +14,27 @@ const isReplayEnabled = (surface: AnalyticsSurface) => ? appData.getAnalyticsRendererReplayEnabled() : appData.getAnalyticsReplayEnabled(); +// A cross-origin script that throws without CORS headers surfaces in the +// browser as an opaque "Script error." with no stack and a synthetic +// mechanism. These come from extensions or third-party scripts, carry no +// stack to debug, and reach error tracking as high-severity noise, so we drop +// them before they leave the browser. +const isOpaqueScriptError = (event: CaptureResult): boolean => { + if (event.event !== "$exception") { + return false; + } + + const exceptionList = event.properties?.$exception_list; + if (!Array.isArray(exceptionList) || exceptionList.length === 0) { + return false; + } + + return exceptionList.every((exception) => { + const frameCount = exception?.stacktrace?.frames?.length ?? 0; + return exception?.value === "Script error." && frameCount === 0; + }); +}; + export const initAnalytics = ({ surface, env }: InitAnalyticsOptions) => { const key = appData.getAnalyticsKey(); @@ -46,6 +67,10 @@ export const initAnalytics = ({ surface, env }: InitAnalyticsOptions) => { return null; } + if (isOpaqueScriptError(event)) { + return null; + } + return { ...event, properties: {