From 6530840dd3f0f00313f497e588acd3bfee7a0163 Mon Sep 17 00:00:00 2001 From: "Krishna (via MelvinBot)" Date: Thu, 17 Sep 2026 11:20:39 +0000 Subject: [PATCH] Ignore injected WKWebView messageHandlers errors in Sentry Sentry APP-8WS reports a TypeError naming top.webkit.messageHandlers.foregroundToBackground.postMessage. No code in the org calls webkit.messageHandlers, so the bridge is injected by an in-app browser and the error is not ours to act on. WebKit withholds the URL of an injected script, so denyUrls and thirdPartyErrorFilterIntegration cannot match it and ignoreErrors is the only filter that can. Co-authored-by: Krishna --- src/setup/telemetry/setupSentry.ts | 8 +++++++ tests/unit/setupSentryTest.ts | 36 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/setup/telemetry/setupSentry.ts b/src/setup/telemetry/setupSentry.ts index 30704aeeb5eb..a977b2863980 100644 --- a/src/setup/telemetry/setupSentry.ts +++ b/src/setup/telemetry/setupSentry.ts @@ -71,6 +71,14 @@ function setupSentry(): void { // `add()` instead of `put()` and fails whenever the key is already there. Onyx never calls `add()`, so a // real Onyx write cannot produce this, and the DOMException carries no frames to tag as third-party. /^ConstraintError: Key already exists in the object store/, + // Calls into the WKWebView message bridge an in-app browser injected into the page, which then tore the + // bridge down (https://github.com/Expensify/App/issues/100268, Sentry APP-8WS). We ship no + // `webkit.messageHandlers` call anywhere in the org, so any error naming one was thrown by injected code + // we cannot act on. Filtering on the message rather than the frames is what works here: WebKit withholds + // the URL of an injected script, so `denyUrls` and `thirdPartyErrorFilterIntegration` have nothing to + // match. The trailing dot keeps this to a real property access into the bridge, so an unrelated error + // that merely names the bridge in prose still reports. + /webkit\.messageHandlers\./, ], denyUrls: EXTENSION_DENY_URLS, beforeSendTransaction: processBeforeSendTransactions, diff --git a/tests/unit/setupSentryTest.ts b/tests/unit/setupSentryTest.ts index 216c402181b8..76063d0fb3fc 100644 --- a/tests/unit/setupSentryTest.ts +++ b/tests/unit/setupSentryTest.ts @@ -70,4 +70,40 @@ describe('setupSentry', () => { // Then it still reports expect(isIgnored(message)).toBe(false); }); + + it('drops the WKWebView bridge call an in-app browser injects into the page', () => { + // Given the message WebKit builds for the APP-8WS rejection, naming the expression whose base was undefined + const message = "TypeError: undefined is not an object (evaluating 'top.webkit.messageHandlers.foregroundToBackground.postMessage')"; + + // When the registered patterns are matched against it + // Then it is ignored, because no code we ship calls `webkit.messageHandlers` + expect(isIgnored(message)).toBe(true); + }); + + it('drops a call into any other injected handler on that same bridge', () => { + // Given the same signature with a different handler name, which is the same injected-bridge noise + const message = "TypeError: undefined is not an object (evaluating 'window.webkit.messageHandlers.contextMenuMessageHandler.postMessage')"; + + // When the registered patterns are matched against it + // Then it is ignored too, rather than needing one pattern per handler name + expect(isIgnored(message)).toBe(true); + }); + + it('keeps an error that only names the bridge in prose, which is not a call into it', () => { + // Given an error that mentions the bridge without accessing a handler on it + const message = 'Error: Failed to set up webkit.messageHandlers'; + + // When the registered patterns are matched against it + // Then it still reports, because the trailing dot limits the pattern to a real property access + expect(isIgnored(message)).toBe(false); + }); + + it('keeps the TypeErrors our own code produces, which read the same way in Safari', () => { + // Given the same WebKit wording for a property access in our bundle + const message = "TypeError: undefined is not an object (evaluating 'policy.employeeList.length')"; + + // When the registered patterns are matched against it + // Then it still reports + expect(isIgnored(message)).toBe(false); + }); });