fix: filter Safari cashback extension errors (#447)

## Sentry
- Issue: PHP-LARAVEL-2K
- URL: https://whisper-money.sentry.io/issues/123306290/

## Root cause
Sentry is collecting unhandled errors from a Safari/iOS browser
extension content script. Recent production events all came from
`webkit-masked-url://hidden/`, function `onResponse`, with message
`response.cashbackReminder`, on public app routes. No matching
application code exists.

## Fix
- Add a narrow client-side Sentry `beforeSend` filter for this Safari
cashback extension signature.
- Keep similarly named errors from application bundles.
- Add Vitest regression coverage.

## Verification
- `npx prettier --write resources/js/app.tsx resources/js/lib/sentry.ts
resources/js/lib/sentry.test.ts`
- `npx eslint --fix resources/js/app.tsx resources/js/lib/sentry.ts
resources/js/lib/sentry.test.ts`
- `npm test -- resources/js/lib/sentry.test.ts`
- `npm run types` (fails on existing unrelated TypeScript errors outside
touched files)
This commit is contained in:
Víctor Falcón 2026-05-28 15:10:49 +02:00 committed by GitHub
parent edb9f1c852
commit 0b9406714e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 1 deletions

View File

@ -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;
}

View File

@ -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 = {

View File

@ -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
);
}