Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions __tests__/utils/sentry-client-filters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
__testing,
shouldFilterByFilenameExceptions,
} from "@/utils/sentry-client-filters";

describe("sentry-client-filters", () => {
it("filters events when a stack frame matches a filename exception", () => {
// Arrange
const frames = [{ filename: "app:///extensionServiceWorker.js" } as any];

// Act
const result = shouldFilterByFilenameExceptions(frames);

// Assert
expect(result).toBe(true);
});

it("filters events when the original exception stack contains a filename exception", () => {
// Arrange
const error = new Error("Cannot read properties of undefined (reading 'ton')");
error.stack = `TypeError: Cannot read properties of undefined (reading 'ton')\n at requestAccounts (app:///extensionServiceWorker.js:75067:1)`;

// Act
const result = shouldFilterByFilenameExceptions(undefined, {
originalException: error,
});

// Assert
expect(result).toBe(true);
});

it("does not filter when frames do not match any filename exception", () => {
// Arrange
const frames = [{ filename: "app:///main.js" } as any];

// Act
const result = shouldFilterByFilenameExceptions(frames);

// Assert
expect(result).toBe(false);
});

it("does not filter when the original exception stack does not match any filename exception", () => {
// Arrange
const error = new Error("Some other error");
error.stack = `Error: Some other error\n at foo (app:///main.js:1:1)`;

// Act
const result = shouldFilterByFilenameExceptions(undefined, {
originalException: error,
});

// Assert
expect(result).toBe(false);
});

it("includes extensionServiceWorker.js in filename exceptions", () => {
// Arrange
const expected = "extensionServiceWorker.js";

// Act
const result = __testing.filenameExceptions;

// Assert
expect(result).toContain(expected);
});
});
1 change: 1 addition & 0 deletions codex/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ This table is the single source of truth for active and historical tickets. Keep
| TKT-0034 | Remove unused findLightDropBySerialNoWithPagination helper | Review | P2 | openai-assistant | — | 2025-10-28 |
| TKT-0035 | Add Discover link to app sidebar navigation | In-Progress | P1 | openai-assistant | — | 2025-10-29 |
| TKT-0036 | Improve wave media image optimisation | In-Progress | P1 | openai-assistant | — | 2025-10-29 |
| TKT-0037 | Filter extensionServiceWorker Sentry noise on /waves | Review | P1 | openai-assistant | — | 2025-12-17 |

## Usage Guidelines

Expand Down
35 changes: 35 additions & 0 deletions codex/tickets/TKT-0037.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
created: 2025-12-17
id: TKT-0037
owner: openai-assistant
priority: P1
status: Review
title: Filter extensionServiceWorker Sentry noise on /waves
---

## Context

> Sentry issue `7073335494` reports `TypeError: Cannot read properties of undefined (reading 'ton')` with stack frames pointing at `app:///extensionServiceWorker.js`. This appears to be browser-extension service worker noise (e.g. wallet/extension messaging) rather than application code. We should filter these events client-side in Sentry `beforeSend` so production error signal remains actionable.

## Plan

- [x] Add a targeted `beforeSend` filter for `extensionServiceWorker.js` stacks/frames.
- [x] Add unit coverage for the filename/stack filtering helper.
- [ ] Run `npm run test`, `npm run lint`, `npm run type-check`.
- [ ] Verify in production Sentry that issue volume drops.

## Acceptance

- [ ] Sentry drops events whose stack includes `app:///extensionServiceWorker.js`.
- [ ] No app-originating errors are filtered unintentionally.
- [ ] `npm run test`, `npm run lint`, `npm run type-check` pass locally.

## Links

- Primary PR: _(add when available)_
- Follow-ups: _(reference additional tickets or TODO items)_

## Log

- 2025-12-17T00:00:00Z – Added a targeted client-side Sentry filter for `extensionServiceWorker.js` and unit tests for the helper.

19 changes: 2 additions & 17 deletions instrumentation-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
INDEXEDDB_ERROR_MESSAGE,
isIndexedDBError,
} from "@/utils/error-sanitizer";
import { shouldFilterByFilenameExceptions } from "@/utils/sentry-client-filters";
import * as Sentry from "@sentry/nextjs";

const sentryEnabled = !!publicEnv.SENTRY_DSN;
Expand All @@ -21,8 +22,6 @@ const noisyPatterns = [

const referenceErrors = ["__firefox__"];

const filenameExceptions = ["inpage.js", "injectLeap.js", "inject.chrome"];

const URL_REGEX = /\(([^)]+?)\)/;

function getFallbackMessage(hint?: Sentry.EventHint): string {
Expand All @@ -49,20 +48,6 @@ function shouldFilterReferenceErrors(
return referenceErrors.some((p) => message.includes(p));
}

function shouldFilterFilenameExceptions(
frames: Sentry.StackFrame[] | undefined
): boolean {
if (!frames) {
return false;
}
return frames.some((frame) =>
filenameExceptions.some(
(pattern) =>
frame?.filename?.includes(pattern) || frame?.abs_path?.includes(pattern)
)
);
}

function shouldFilterEvent(
event: Sentry.Event,
hint?: Sentry.EventHint
Expand All @@ -81,7 +66,7 @@ function shouldFilterEvent(
}

const frames = event.exception?.values?.[0]?.stacktrace?.frames;
return shouldFilterFilenameExceptions(frames);
return shouldFilterByFilenameExceptions(frames, hint);
}

function handleIndexedDBError(event: Sentry.Event): void {
Expand Down
42 changes: 42 additions & 0 deletions utils/sentry-client-filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export type SentryStackFrame = { filename?: string; abs_path?: string };
export type SentryEventHint = {
originalException?: unknown;
syntheticException?: unknown;
};

const filenameExceptions = ["inpage.js", "extensionServiceWorker.js", "injectLeap.js", "inject.chrome"];

function shouldFilterFilenameExceptions(
frames: SentryStackFrame[] | undefined
): boolean {
if (!frames) {
return false;
}
return frames.some((frame) =>
filenameExceptions.some(
(pattern) =>
frame?.filename?.includes(pattern) || frame?.abs_path?.includes(pattern)
)
);
}

function shouldFilterExceptionStack(hint?: SentryEventHint): boolean {
const exception = hint?.originalException ?? hint?.syntheticException;
if (!(exception instanceof Error)) {
return false;
}
const stack = exception.stack;
if (typeof stack !== "string") {
return false;
}
return filenameExceptions.some((pattern) => stack.includes(pattern));
}

export function shouldFilterByFilenameExceptions(
frames: SentryStackFrame[] | undefined,
hint?: SentryEventHint
): boolean {
return shouldFilterFilenameExceptions(frames) || shouldFilterExceptionStack(hint);
}

export const __testing = { filenameExceptions };