diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 8fdf5698..651c4924 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -20,6 +20,7 @@ import { SyncProvider } from './contexts/sync-context'; import { initializeTheme } from './hooks/use-appearance'; import { initializeChartColorScheme } from './hooks/use-chart-color-scheme'; import { initializePostHog } from './lib/posthog'; +import { isPostMessageDataCloneNoise } from './lib/sentry'; import type { SharedData } from './types'; import { setTranslations } from './utils/i18n'; @@ -29,6 +30,13 @@ Sentry.init({ integrations: [], tracesSampleRate: 0, sendDefaultPii: true, + beforeSend(event) { + if (isPostMessageDataCloneNoise(event)) { + return null; + } + + return event; + }, enabled: import.meta.env.PROD && Boolean(import.meta.env.SENTRY_LARAVEL_DSN), }); diff --git a/resources/js/lib/sentry.test.ts b/resources/js/lib/sentry.test.ts new file mode 100644 index 00000000..b8816542 --- /dev/null +++ b/resources/js/lib/sentry.test.ts @@ -0,0 +1,49 @@ +import type { Event } from '@sentry/react'; +import { describe, expect, it } from 'vitest'; +import { isPostMessageDataCloneNoise } from './sentry'; + +describe('isPostMessageDataCloneNoise', () => { + it('drops browser postMessage DataCloneError noise', () => { + const event: Event = { + exception: { + values: [ + { + type: 'DataCloneError', + value: 'The object can not be cloned.', + stacktrace: { + frames: [ + { + function: 'Window.postMessage', + }, + ], + }, + }, + ], + }, + }; + + expect(isPostMessageDataCloneNoise(event)).toBe(true); + }); + + it('keeps other DataCloneError events without postMessage frames', () => { + const event: Event = { + exception: { + values: [ + { + type: 'DataCloneError', + value: 'The object can not be cloned.', + stacktrace: { + frames: [ + { + function: 'structuredClone', + }, + ], + }, + }, + ], + }, + }; + + expect(isPostMessageDataCloneNoise(event)).toBe(false); + }); +}); diff --git a/resources/js/lib/sentry.ts b/resources/js/lib/sentry.ts new file mode 100644 index 00000000..d4f69f80 --- /dev/null +++ b/resources/js/lib/sentry.ts @@ -0,0 +1,24 @@ +import type { Event } from '@sentry/react'; + +const CLONE_ERROR_MESSAGE_PATTERN = + /object (can not|could not|couldn't|can't) be cloned/i; + +export function isPostMessageDataCloneNoise(event: Event): boolean { + return ( + event.exception?.values?.some((exception) => { + const exceptionType = exception.type ?? ''; + const exceptionValue = exception.value ?? ''; + const frames = exception.stacktrace?.frames ?? []; + + return ( + exceptionType === 'DataCloneError' && + CLONE_ERROR_MESSAGE_PATTERN.test(exceptionValue) && + frames.some((frame) => + [frame.function, frame.filename, frame.module].some( + (value) => value?.includes('postMessage'), + ), + ) + ); + }) ?? false + ); +}