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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { filterPosthogBeforeSend } from "./posthog-exception-filter.util";
import { describe, expect, it } from "bun:test";

const exceptionEvent = (
entries: Array<{ type?: string; value?: string }>,
entries: Array<{
type?: string;
value?: string;
stacktrace?: { frames?: Array<Record<string, unknown>> };
}>,
): CaptureResult =>
({
uuid: "test-uuid",
Expand Down Expand Up @@ -58,6 +62,27 @@ describe("filterPosthogBeforeSend", () => {
).toBeNull();
});

it("drops opaque browser Script error. with no stack frames", () => {
expect(
filterPosthogBeforeSend(
exceptionEvent([{ type: "Error", value: "Script error." }]),
),
).toBeNull();
});

it("keeps Script error. when stack frames are present", () => {
const event = exceptionEvent([
{
type: "Error",
value: "Script error.",
stacktrace: {
frames: [{ filename: "/index.js", function: "boot" }],
},
},
]);
expect(filterPosthogBeforeSend(event)).toBe(event);
});

it("keeps ApiError exceptions", () => {
const event = exceptionEvent([
{ type: "ApiError", value: "Request failed with status 500" },
Expand Down
22 changes: 22 additions & 0 deletions packages/web/src/auth/posthog/posthog-exception-filter.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ import { isTransientBrowserNetworkMessage } from "@web/api/util/backend-unavaila
const CEFSHARP_SCANNER_MESSAGE =
"Object Not Found Matching Id:1, MethodName:update, ParamCount:4";

/**
* Browser-sanitized cross-origin `window.onerror` message. Same-origin app
* scripts (this app's `/index.js` module) report real messages + frames;
* "Script error." with no stack is almost always an extension, in-app browser,
* or other third-party script — never actionable from our code alone.
*/
const SCRIPT_ERROR_MESSAGE = "Script error.";

type ExceptionEntry = {
type?: unknown;
value?: unknown;
stacktrace?: {
frames?: unknown[];
};
};

const readExceptionEntries = (
Expand Down Expand Up @@ -40,11 +51,22 @@ const readExceptionEntries = (
}));
};

const hasStackFrames = (entry: ExceptionEntry): boolean => {
const frames = entry.stacktrace?.frames;
return Array.isArray(frames) && frames.length > 0;
};

const isDroppableException = (entry: ExceptionEntry): boolean => {
if (typeof entry.value !== "string") return false;

if (entry.value === CEFSHARP_SCANNER_MESSAGE) return true;

// Opaque cross-origin errors. Keep the rare case where frames somehow
// survived sanitization so a real stack is never discarded.
if (entry.value === SCRIPT_ERROR_MESSAGE && !hasStackFrames(entry)) {
return true;
}

// SuperTokens/browser network blips that escape as unhandledrejections.
// The app already treats these as expected unavailability; capturing them
// only creates noise. SuperTokens' session fetch often rejects outside our
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/auth/posthog/posthog.bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export function initPosthog(): PostHog | undefined {
capture_console_errors: false,
},
// Drop known-unactionable exception signatures (SuperTokens/browser
// network blips, CefSharp scanner noise) before they become issues.
// network blips, CefSharp scanner noise, opaque "Script error.") before
// they become issues.
before_send: filterPosthogBeforeSend,
opt_in_site_apps: true,
person_profiles: "always",
Expand Down