diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 5f15c2b4..cea1d9f1 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -25,6 +25,7 @@ import { isChunkLoadErrorEvent, isFacebookInAppBrowserJavaBridgeNoise, isPostMessageDataCloneNoise, + isSafariCashbackExtensionNoise, } from './lib/sentry'; import { showSubscriptionPaymentIssueToast } from './lib/subscription-payment-issue-toast'; import type { ExpiredBankingConnectionNotification, SharedData } from './types'; @@ -42,7 +43,8 @@ Sentry.init({ if ( isChunkLoadErrorEvent(event) || isPostMessageDataCloneNoise(event) || - isFacebookInAppBrowserJavaBridgeNoise(event) + isFacebookInAppBrowserJavaBridgeNoise(event) || + isSafariCashbackExtensionNoise(event) ) { return null; } diff --git a/resources/js/lib/sentry.test.ts b/resources/js/lib/sentry.test.ts index 91596eb8..f2aca5e4 100644 --- a/resources/js/lib/sentry.test.ts +++ b/resources/js/lib/sentry.test.ts @@ -4,6 +4,7 @@ import { isChunkLoadErrorEvent, isFacebookInAppBrowserJavaBridgeNoise, isPostMessageDataCloneNoise, + isSafariCashbackExtensionNoise, } from './sentry'; describe('isChunkLoadErrorEvent', () => { @@ -87,6 +88,54 @@ describe('isFacebookInAppBrowserJavaBridgeNoise', () => { }); }); +describe('isSafariCashbackExtensionNoise', () => { + it('drops Safari cashback extension response handler errors', () => { + const event: Event = { + exception: { + values: [ + { + type: 'TypeError', + value: "undefined is not an object (evaluating 'response.cashbackReminder')", + stacktrace: { + frames: [ + { + filename: 'webkit-masked-url://hidden/', + function: 'onResponse', + }, + ], + }, + }, + ], + }, + }; + + expect(isSafariCashbackExtensionNoise(event)).toBe(true); + }); + + it('keeps cashback errors from application code', () => { + const event: Event = { + exception: { + values: [ + { + type: 'TypeError', + value: "undefined is not an object (evaluating 'response.cashbackReminder')", + stacktrace: { + frames: [ + { + filename: '/build/assets/app.js', + function: 'handleResponse', + }, + ], + }, + }, + ], + }, + }; + + expect(isSafariCashbackExtensionNoise(event)).toBe(false); + }); +}); + describe('isPostMessageDataCloneNoise', () => { it('drops browser postMessage DataCloneError noise', () => { const event: Event = { diff --git a/resources/js/lib/sentry.ts b/resources/js/lib/sentry.ts index aac1f5f1..b8bed60d 100644 --- a/resources/js/lib/sentry.ts +++ b/resources/js/lib/sentry.ts @@ -5,6 +5,7 @@ const CLONE_ERROR_MESSAGE_PATTERN = /object (can not|could not|couldn't|can't) be cloned/i; const FACEBOOK_IAB_JAVA_OBJECT_GONE_PATTERN = /Error invoking .+: Java object is gone/i; +const SAFARI_CASHBACK_EXTENSION_PATTERN = /response\.cashbackReminder/i; export function isChunkLoadErrorEvent(event: Event): boolean { return ( @@ -55,3 +56,21 @@ export function isFacebookInAppBrowserJavaBridgeNoise(event: Event): boolean { }) ?? false ); } + +export function isSafariCashbackExtensionNoise(event: Event): boolean { + return ( + event.exception?.values?.some((exception) => { + const exceptionValue = exception.value ?? ''; + const frames = exception.stacktrace?.frames ?? []; + + return ( + SAFARI_CASHBACK_EXTENSION_PATTERN.test(exceptionValue) && + frames.some( + (frame) => + frame.function === 'onResponse' && + frame.filename === 'webkit-masked-url://hidden/', + ) + ); + }) ?? false + ); +}