Files
multica/packages/core/analytics/benign-exceptions.test.ts
Naiyuan Qing 9eaea31892 fix(realtime): guard WS frames without a string type + drop benign ResizeObserver noise (MUL-3418) (#4304)
* fix(realtime): drop WS frames without a string type (MUL-3418)

The onmessage handler dispatched every parsed frame to onAny and the
ws.on subscribers without validating its shape. A frame whose parsed JSON
lacked a string `type` (an out-of-protocol frame injected by a proxy /
extension, or a bare JSON primitive) reached the realtime-sync onAny
dispatcher, where `msg.type.split(":")` threw an uncaught TypeError out of
onmessage. The browser reported it via window.onerror, flooding PostHog
`$exception` (~12k events). Non-fatal (no crash, connection stays up), but
pure noise.

Validate once at this trust boundary: a frame must be an object carrying a
string `type`, otherwise drop it as a no-op. The bad-frame log is
rate-limited to one entry per connection — a misbehaving source can repeat
the frame hundreds of times per session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* feat(analytics): drop benign ResizeObserver exceptions from telemetry

ResizeObserver "loop ..." errors are the dominant `$exception` bucket
(~31k events / ~1.5k users). They are a benign, self-recovering browser
quirk with no actionable signal and otherwise drown real failures and burn
the event budget. Drop them entirely in before_send (ahead of redaction
and the dedupe fuse, which only caps repeats). The match is narrow — only
the benign "loop" phrasing — so a genuine ResizeObserver bug still reports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-18 17:24:48 +08:00

73 lines
2.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { isBenignException } from "./benign-exceptions";
describe("isBenignException", () => {
it("drops ResizeObserver loop errors via $exception_list value", () => {
expect(
isBenignException({
$exception_list: [
{
type: "Error",
value: "ResizeObserver loop completed with undelivered notifications.",
},
],
}),
).toBe(true);
});
it("drops the older 'loop limit exceeded' phrasing", () => {
expect(
isBenignException({
$exception_list: [
{ type: "Error", value: "ResizeObserver loop limit exceeded" },
],
}),
).toBe(true);
});
it("drops when the signal is on the top-level $exception_message", () => {
expect(
isBenignException({
$exception_message: "ResizeObserver loop limit exceeded",
}),
).toBe(true);
});
it("matches case-insensitively", () => {
expect(
isBenignException({ $exception_message: "resizeobserver LOOP limit exceeded" }),
).toBe(true);
});
it("keeps real errors", () => {
expect(
isBenignException({
$exception_list: [
{
type: "TypeError",
value: "Cannot read properties of undefined (reading 'split')",
},
],
}),
).toBe(false);
});
it("does not match an unrelated mention of ResizeObserver", () => {
// Only the benign "loop" phrasing is silenced; a genuine bug in
// ResizeObserver usage must still be reported.
expect(
isBenignException({
$exception_message: "ResizeObserver is not defined",
}),
).toBe(false);
});
it("fails open on missing or malformed properties", () => {
expect(isBenignException(undefined)).toBe(false);
expect(isBenignException({})).toBe(false);
expect(isBenignException({ $exception_list: "not-an-array" })).toBe(false);
expect(isBenignException({ $exception_list: [null, 42, {}] })).toBe(false);
expect(isBenignException({ $exception_message: 123 })).toBe(false);
});
});