From a4f8662350bde10cbc33314bd7174aff9d56a09a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 15:58:58 +0000 Subject: [PATCH 1/5] Drop crashes thrown by code injected inline into our pages The /results crash on the bug board ("TypeError: undefined is not an object", Brave on macOS) has a single frame, "global code@https://fundermatch.org/results:1:16": inline code in the page document itself. Our CSP allows no inline scripts and all our code is served from /assets/*.js, so that code was injected by the browser or an extension (a user script), which content scripts can do despite the CSP. isNoise now treats a crash whose top frame is one of our own pages (not a script file) like one whose top frame is an extension's. Frames from our bundle above it still count as ours, and other sites' URLs are untouched. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GNg1jsnDVcemV3Ut9XhA7h --- src/lib/monitoring.ts | 16 +++++++++++----- tests/monitoring.test.mjs | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/lib/monitoring.ts b/src/lib/monitoring.ts index cfdff58..9fa0f1b 100644 --- a/src/lib/monitoring.ts +++ b/src/lib/monitoring.ts @@ -49,6 +49,11 @@ export interface VitalsReport { metrics: { id: string; name: string; value: number; rating: string; path: string; seq: number }[]; } +// Only the live site reports: a production build served anywhere else (a +// preview deploy, `vite preview`, a QA machine) would put its crashes and +// timings on the real board and in the real p75s. +const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; + // ── Pure helpers (unit-tested) ────────────────────────────────────────────── /** @@ -76,6 +81,12 @@ export function isNoise(error: unknown, message: string, stack: string, chunkGav // addEventListener appears below our own frames in real app crashes. const topFrame = stack.split('\n').find((line) => FRAME.test(line) && /\w+:\/\//.test(line)) ?? ''; if (/(?:chrome|moz|safari(?:-web)?)-extension:\/\//i.test(topFrame)) return true; + // Injected into the page itself (a browser's or extension's user script, + // e.g. "global code@https://fundermatch.org/results:1:16"): its top frame + // is one of our pages, not a script file. Our CSP allows no inline + // scripts, and all our code is served as /assets/*.js. + const topUrl = /https?:\/\/([^/\s():]+)(\/[^\s():?#]*)?/i.exec(topFrame); + if (topUrl && PRODUCTION_HOST.test(topUrl[1]) && !/\.[cm]?js$/i.test(topUrl[2] ?? '')) return true; // Lost connections and cancelled requests, in each browser's wording: the // network, not our code. Unless the error screen showed: a page that breaks // when a request fails is a bug worth a card. @@ -142,11 +153,6 @@ export function buildCrashReport( // ── Sending ───────────────────────────────────────────────────────────────── -// Only the live site reports: a production build served anywhere else (a -// preview deploy, `vite preview`, a QA machine) would put its crashes and -// timings on the real board and in the real p75s. -const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; - function enabled(): boolean { return !import.meta.env.DEV && typeof window !== 'undefined' && PRODUCTION_HOST.test(window.location.hostname); } diff --git a/tests/monitoring.test.mjs b/tests/monitoring.test.mjs index a76d698..aebd000 100644 --- a/tests/monitoring.test.mjs +++ b/tests/monitoring.test.mjs @@ -112,6 +112,28 @@ test('noise: extensions, opaque cross-origin errors, ResizeObserver', () => { assert.equal(isNoise(new TypeError('x is undefined'), 'x is undefined', ' at f (https://fundermatch.org/assets/a.js:1:1)'), false); }); +test('noise: code injected inline into our page by the browser or an extension', () => { + // The bug board's /results crash (Brave on macOS): the only frame is + // inline code in the page itself. Our CSP allows no inline scripts, so + // nothing of ours runs there. + const msg = "undefined is not an object (evaluating 'window.webkit.messageHandlers')"; + assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org/results:1:16'), true); + assert.equal(buildCrashReport('error', new TypeError(msg), '/results', 'b', '', false, { name: 'TypeError', message: msg, stack: 'global code@https://fundermatch.org/results:1:16' }), null); + // Chrome's frame wording, the home page, www, and a page with a trailing slash. + assert.equal(isNoise(new TypeError(msg), msg, `TypeError: ${msg}\n at https://fundermatch.org/results:1:16`), true); + assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org/:3:7'), true); + assert.equal(isNoise(new TypeError(msg), msg, ' at f (https://www.fundermatch.org/search/:1:1)'), true); + // An error event with no error object, as installMonitoring builds it. + assert.equal(buildCrashReport('error', { name: 'TypeError', message: msg, stack: ' at https://fundermatch.org/results:1:16' }, '/results', 'b'), null); + // Our bundle's frames are ours, even with an inline frame further down. + assert.equal(isNoise(new TypeError(msg), msg, 'f@https://fundermatch.org/assets/index-jLhmJpd-.js:1:2'), false); + assert.equal(isNoise(new TypeError(msg), msg, 'f@https://fundermatch.org/assets/a.mjs:1:2\nglobal code@https://fundermatch.org/results:1:16'), false); + // Another site's extensionless script URL isn't our page. + assert.equal(isNoise(new TypeError(msg), msg, ' at f (https://maps.googleapis.com/maps/api/js?key=k:1:1)'), false); + // Nor is a look-alike host. + assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org.evil.test/results:1:1'), false); +}); + test('noise: the Outlook Safe Links crawler\'s string rejections', () => { for (const msg of ['Object Not Found Matching Id:3, MethodName:update, ParamCount:4', 'Object Not Found Matching Id:12, MethodName:simulateEvent, ParamCount:1']) { assert.equal(isNoise(msg, msg, ''), true, msg); From 8cf48ceca353c4f127f043fb730ff2ac46f06e74 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 16:02:13 +0000 Subject: [PATCH 2/5] Match our pages by origin and /assets/ in one pattern Review feedback: an explicit :443 left the path unparsed, so a frame from our own bundle counted as injected; and the rule now says what it means (our code is served from /assets/) instead of keying on a .js extension. PRODUCTION_HOST goes back where it was. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GNg1jsnDVcemV3Ut9XhA7h --- src/lib/monitoring.ts | 17 ++++++++--------- tests/monitoring.test.mjs | 3 +++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/monitoring.ts b/src/lib/monitoring.ts index 9fa0f1b..f36fc1b 100644 --- a/src/lib/monitoring.ts +++ b/src/lib/monitoring.ts @@ -49,11 +49,6 @@ export interface VitalsReport { metrics: { id: string; name: string; value: number; rating: string; path: string; seq: number }[]; } -// Only the live site reports: a production build served anywhere else (a -// preview deploy, `vite preview`, a QA machine) would put its crashes and -// timings on the real board and in the real p75s. -const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; - // ── Pure helpers (unit-tested) ────────────────────────────────────────────── /** @@ -83,10 +78,9 @@ export function isNoise(error: unknown, message: string, stack: string, chunkGav if (/(?:chrome|moz|safari(?:-web)?)-extension:\/\//i.test(topFrame)) return true; // Injected into the page itself (a browser's or extension's user script, // e.g. "global code@https://fundermatch.org/results:1:16"): its top frame - // is one of our pages, not a script file. Our CSP allows no inline - // scripts, and all our code is served as /assets/*.js. - const topUrl = /https?:\/\/([^/\s():]+)(\/[^\s():?#]*)?/i.exec(topFrame); - if (topUrl && PRODUCTION_HOST.test(topUrl[1]) && !/\.[cm]?js$/i.test(topUrl[2] ?? '')) return true; + // is one of our pages, not our bundle. Our CSP allows no inline scripts, + // and all our code is served from /assets/. + if (/^https?:\/\/(?:www\.)?fundermatch\.org(?::443)?\/(?!assets\/)/i.test(/https?:\/\/\S+/i.exec(topFrame)?.[0] ?? '')) return true; // Lost connections and cancelled requests, in each browser's wording: the // network, not our code. Unless the error screen showed: a page that breaks // when a request fails is a bug worth a card. @@ -153,6 +147,11 @@ export function buildCrashReport( // ── Sending ───────────────────────────────────────────────────────────────── +// Only the live site reports: a production build served anywhere else (a +// preview deploy, `vite preview`, a QA machine) would put its crashes and +// timings on the real board and in the real p75s. +const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; + function enabled(): boolean { return !import.meta.env.DEV && typeof window !== 'undefined' && PRODUCTION_HOST.test(window.location.hostname); } diff --git a/tests/monitoring.test.mjs b/tests/monitoring.test.mjs index aebd000..f3eb9e6 100644 --- a/tests/monitoring.test.mjs +++ b/tests/monitoring.test.mjs @@ -128,6 +128,9 @@ test('noise: code injected inline into our page by the browser or an extension', // Our bundle's frames are ours, even with an inline frame further down. assert.equal(isNoise(new TypeError(msg), msg, 'f@https://fundermatch.org/assets/index-jLhmJpd-.js:1:2'), false); assert.equal(isNoise(new TypeError(msg), msg, 'f@https://fundermatch.org/assets/a.mjs:1:2\nglobal code@https://fundermatch.org/results:1:16'), false); + // An explicit default port is still our bundle, or still our page. + assert.equal(isNoise(new TypeError(msg), msg, 'f@https://fundermatch.org:443/assets/index-x.js:1:2'), false); + assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org:443/results:1:16'), true); // Another site's extensionless script URL isn't our page. assert.equal(isNoise(new TypeError(msg), msg, ' at f (https://maps.googleapis.com/maps/api/js?key=k:1:1)'), false); // Nor is a look-alike host. From 4fdc925a4829f58264cd5f6da527ef16ce6f11a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 16:05:28 +0000 Subject: [PATCH 3/5] Keep error-screen crashes; reuse PRODUCTION_HOST for the page check Review round 2: a crash that brought up the error screen is reported even when its top frame is a page, like the network rule. The page check parses the frame's URL and reuses PRODUCTION_HOST, so any port or scheme works and the host lives in one place. Tests pin the error-screen case and Safari's failed module import pinned on the document (still a chunk-load error). The CSP comment in index.html points at the rule that depends on it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GNg1jsnDVcemV3Ut9XhA7h --- index.html | 4 ++++ src/lib/monitoring.ts | 32 ++++++++++++++++++++++---------- tests/monitoring.test.mjs | 10 ++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 90770cf..d74ef1b 100644 --- a/index.html +++ b/index.html @@ -37,6 +37,10 @@ production bundle, so the previous cdn.jsdelivr.net / cdnjs.cloudflare.com allowances have been dropped to keep CSP tight. + Note: crash reporting (src/lib/monitoring.ts, isNoise) drops errors + thrown from inline code in a page, relying on there being none of + ours. Allowing an inline script here means revisiting that rule. + style-src: 'self' – Tailwind CSS bundle 'unsafe-inline' – Tailwind utility classes applied as inline styles by React diff --git a/src/lib/monitoring.ts b/src/lib/monitoring.ts index f36fc1b..085e022 100644 --- a/src/lib/monitoring.ts +++ b/src/lib/monitoring.ts @@ -51,6 +51,22 @@ export interface VitalsReport { // ── Pure helpers (unit-tested) ────────────────────────────────────────────── +// Only the live site reports: a production build served anywhere else (a +// preview deploy, `vite preview`, a QA machine) would put its crashes and +// timings on the real board and in the real p75s. +const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; + +/** A URL on the live site outside /assets/: a page, not our bundle. */ +function isOurPage(url: string | undefined): boolean { + if (!url) return false; + try { + const { hostname, pathname } = new URL(url); + return PRODUCTION_HOST.test(hostname) && !pathname.startsWith('/assets/'); + } catch { + return false; + } +} + /** * Errors that aren't ours to fix: browser extensions, the opaque * cross-origin "Script error.", ResizeObserver's benign loop warning, the @@ -76,15 +92,16 @@ export function isNoise(error: unknown, message: string, stack: string, chunkGav // addEventListener appears below our own frames in real app crashes. const topFrame = stack.split('\n').find((line) => FRAME.test(line) && /\w+:\/\//.test(line)) ?? ''; if (/(?:chrome|moz|safari(?:-web)?)-extension:\/\//i.test(topFrame)) return true; - // Injected into the page itself (a browser's or extension's user script, - // e.g. "global code@https://fundermatch.org/results:1:16"): its top frame - // is one of our pages, not our bundle. Our CSP allows no inline scripts, - // and all our code is served from /assets/. - if (/^https?:\/\/(?:www\.)?fundermatch\.org(?::443)?\/(?!assets\/)/i.test(/https?:\/\/\S+/i.exec(topFrame)?.[0] ?? '')) return true; // Lost connections and cancelled requests, in each browser's wording: the // network, not our code. Unless the error screen showed: a page that breaks // when a request fails is a bug worth a card. if (screenShown) return false; + // Injected into the page itself (a browser's or extension's user script, + // e.g. "global code@https://fundermatch.org/results:1:16"): its top frame + // is one of our pages, not our bundle. Our CSP allows no inline scripts + // (index.html, public/_headers, public/_worker.js), and all our code is + // served from /assets/. Unless the error screen showed, as above. + if (isOurPage(/https?:\/\/[^\s()]+/i.exec(topFrame)?.[0])) return true; // Some versions add the host: "Failed to fetch (api.example.org)". Safari // also says why ("The network connection was lost."), and supabase-js // wraps a failed Edge Function request in its own wording. @@ -147,11 +164,6 @@ export function buildCrashReport( // ── Sending ───────────────────────────────────────────────────────────────── -// Only the live site reports: a production build served anywhere else (a -// preview deploy, `vite preview`, a QA machine) would put its crashes and -// timings on the real board and in the real p75s. -const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; - function enabled(): boolean { return !import.meta.env.DEV && typeof window !== 'undefined' && PRODUCTION_HOST.test(window.location.hostname); } diff --git a/tests/monitoring.test.mjs b/tests/monitoring.test.mjs index f3eb9e6..c7923ae 100644 --- a/tests/monitoring.test.mjs +++ b/tests/monitoring.test.mjs @@ -135,6 +135,16 @@ test('noise: code injected inline into our page by the browser or an extension', assert.equal(isNoise(new TypeError(msg), msg, ' at f (https://maps.googleapis.com/maps/api/js?key=k:1:1)'), false); // Nor is a look-alike host. assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org.evil.test/results:1:1'), false); + // A crash that brought up the error screen is still worth a card. + assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org/results:1:16', false, true), false); + const shown = new TypeError(msg); + shown.stack = 'global code@https://fundermatch.org/results:1:16'; + assert.equal(buildCrashReport('boundary', shown, '/results', 'b').name, 'TypeError'); + // Safari's failed entry/chunk import, pinned on the document: still a + // chunk-load error, reported once the automatic reload didn't help. + const load = { name: 'TypeError', message: 'Importing a module script failed.', stack: ' at https://fundermatch.org/results:0:0' }; + assert.equal(buildCrashReport('error', load, '/results', 'b'), null); + assert.equal(buildCrashReport('error', load, '/results', 'b', '', true).name, 'TypeError'); }); test('noise: the Outlook Safe Links crawler\'s string rejections', () => { From 3aa5eb5c62cca595e5be1a48b0a2e6d69c492a6b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 16:08:34 +0000 Subject: [PATCH 4/5] Treat only pages, not same-origin scripts, as injected code Review round 3: a same-origin script file outside /assets/ is not a page, so it no longer counts as injected; the check sits with its own comment instead of splitting the network rule's; a test pins Vite's base and asset dir, which the check relies on; and the CSP notes sit beside all three policies. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GNg1jsnDVcemV3Ut9XhA7h --- public/_headers | 2 ++ public/_worker.js | 2 ++ src/lib/monitoring.ts | 16 ++++++++-------- tests/monitoring.test.mjs | 21 ++++++++++++++++++--- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/public/_headers b/public/_headers index d3414da..6043871 100644 --- a/public/_headers +++ b/public/_headers @@ -28,6 +28,8 @@ # detaches it (mirrored by headers.delete() in _worker.js for # worker-served routes). +# CSP: no inline scripts. Crash reporting (src/lib/monitoring.ts, isNoise) +# drops errors thrown from inline code in a page as injected. /* ! Access-Control-Allow-Origin Strict-Transport-Security: max-age=63072000; includeSubDomains; preload diff --git a/public/_worker.js b/public/_worker.js index 95118e6..ebda9fb 100644 --- a/public/_worker.js +++ b/public/_worker.js @@ -36,6 +36,8 @@ const SECURITY_HEADERS = { 'storage-access=()', 'usb=()', 'web-share=(self)', 'window-management=()', 'xr-spatial-tracking=()', ].join(', '), + // No inline scripts: crash reporting (src/lib/monitoring.ts, isNoise) + // drops errors thrown from inline code in a page as injected. 'Content-Security-Policy': [ "default-src 'none'", "script-src 'self' https://gc.zgo.at", diff --git a/src/lib/monitoring.ts b/src/lib/monitoring.ts index 085e022..26c65b8 100644 --- a/src/lib/monitoring.ts +++ b/src/lib/monitoring.ts @@ -56,12 +56,12 @@ export interface VitalsReport { // timings on the real board and in the real p75s. const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; -/** A URL on the live site outside /assets/: a page, not our bundle. */ +/** A URL on the live site that is a page, not a script (ours are in /assets/). */ function isOurPage(url: string | undefined): boolean { if (!url) return false; try { const { hostname, pathname } = new URL(url); - return PRODUCTION_HOST.test(hostname) && !pathname.startsWith('/assets/'); + return PRODUCTION_HOST.test(hostname) && !pathname.startsWith('/assets/') && !/\.[cm]?js(?::\d+)*$/i.test(pathname); } catch { return false; } @@ -92,16 +92,16 @@ export function isNoise(error: unknown, message: string, stack: string, chunkGav // addEventListener appears below our own frames in real app crashes. const topFrame = stack.split('\n').find((line) => FRAME.test(line) && /\w+:\/\//.test(line)) ?? ''; if (/(?:chrome|moz|safari(?:-web)?)-extension:\/\//i.test(topFrame)) return true; + // Injected into the page itself (a browser's or extension's user script, + // e.g. "global code@https://fundermatch.org/results:1:16"): its top frame + // is one of our pages, not a script. Our CSP allows no inline scripts + // (index.html, public/_headers, public/_worker.js). Unless the error + // screen showed: what the visitor saw is worth a card. + if (!screenShown && isOurPage(/https?:\/\/[^\s()]+/i.exec(topFrame)?.[0])) return true; // Lost connections and cancelled requests, in each browser's wording: the // network, not our code. Unless the error screen showed: a page that breaks // when a request fails is a bug worth a card. if (screenShown) return false; - // Injected into the page itself (a browser's or extension's user script, - // e.g. "global code@https://fundermatch.org/results:1:16"): its top frame - // is one of our pages, not our bundle. Our CSP allows no inline scripts - // (index.html, public/_headers, public/_worker.js), and all our code is - // served from /assets/. Unless the error screen showed, as above. - if (isOurPage(/https?:\/\/[^\s()]+/i.exec(topFrame)?.[0])) return true; // Some versions add the host: "Failed to fetch (api.example.org)". Safari // also says why ("The network connection was lost."), and supabase-js // wraps a failed Edge Function request in its own wording. diff --git a/tests/monitoring.test.mjs b/tests/monitoring.test.mjs index c7923ae..a7e2040 100644 --- a/tests/monitoring.test.mjs +++ b/tests/monitoring.test.mjs @@ -112,6 +112,19 @@ test('noise: extensions, opaque cross-origin errors, ResizeObserver', () => { assert.equal(isNoise(new TypeError('x is undefined'), 'x is undefined', ' at f (https://fundermatch.org/assets/a.js:1:1)'), false); }); +// The page check above relies on Vite serving our bundle from /assets/. +test("the bundle is served from /assets/, as isNoise's page check assumes", () => { + const config = readFileSync(new URL('../vite.config.ts', import.meta.url), 'utf8'); + assert.match(config, /base: '\/'/); + assert.doesNotMatch(config, /assetsDir/); +}); + +const shownLike = (msg) => { + const e = new TypeError(msg); + e.stack = 'global code@https://fundermatch.org/results:1:16'; + return e; +}; + test('noise: code injected inline into our page by the browser or an extension', () => { // The bug board's /results crash (Brave on macOS): the only frame is // inline code in the page itself. Our CSP allows no inline scripts, so @@ -135,11 +148,13 @@ test('noise: code injected inline into our page by the browser or an extension', assert.equal(isNoise(new TypeError(msg), msg, ' at f (https://maps.googleapis.com/maps/api/js?key=k:1:1)'), false); // Nor is a look-alike host. assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org.evil.test/results:1:1'), false); + // A same-origin script file outside /assets/ (a CDN-injected one) isn't a page. + assert.equal(isNoise(new TypeError(msg), msg, 'f@https://fundermatch.org/cdn-cgi/scripts/main.js:3:1'), false); + // An unhandled rejection from injected code is noise too. + assert.equal(buildCrashReport('rejection', shownLike(msg), '/results', 'b'), null); // A crash that brought up the error screen is still worth a card. assert.equal(isNoise(new TypeError(msg), msg, 'global code@https://fundermatch.org/results:1:16', false, true), false); - const shown = new TypeError(msg); - shown.stack = 'global code@https://fundermatch.org/results:1:16'; - assert.equal(buildCrashReport('boundary', shown, '/results', 'b').name, 'TypeError'); + assert.equal(buildCrashReport('boundary', shownLike(msg), '/results', 'b').name, 'TypeError'); // Safari's failed entry/chunk import, pinned on the document: still a // chunk-load error, reported once the automatic reload didn't help. const load = { name: 'TypeError', message: 'Importing a module script failed.', stack: ' at https://fundermatch.org/results:0:0' }; From 329ae9c01eafd4e166cda05a1ce86077cb3d6b97 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 16:11:44 +0000 Subject: [PATCH 5/5] Drop the redundant /assets/ clause from the page check Review round 4: every file of our bundle ends in .js, so the extension test alone tells a script from a page, and the test pinning vite.config's text (which checked the source, not the build) goes with it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GNg1jsnDVcemV3Ut9XhA7h --- src/lib/monitoring.ts | 4 ++-- tests/monitoring.test.mjs | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/lib/monitoring.ts b/src/lib/monitoring.ts index 26c65b8..12ff01d 100644 --- a/src/lib/monitoring.ts +++ b/src/lib/monitoring.ts @@ -56,12 +56,12 @@ export interface VitalsReport { // timings on the real board and in the real p75s. const PRODUCTION_HOST = /^(?:www\.)?fundermatch\.org$/; -/** A URL on the live site that is a page, not a script (ours are in /assets/). */ +/** A URL on the live site that is a page, not a script (a frame's :line:col may follow). */ function isOurPage(url: string | undefined): boolean { if (!url) return false; try { const { hostname, pathname } = new URL(url); - return PRODUCTION_HOST.test(hostname) && !pathname.startsWith('/assets/') && !/\.[cm]?js(?::\d+)*$/i.test(pathname); + return PRODUCTION_HOST.test(hostname) && !/\.[cm]?js(?::\d+)*$/i.test(pathname); } catch { return false; } diff --git a/tests/monitoring.test.mjs b/tests/monitoring.test.mjs index a7e2040..07a00a0 100644 --- a/tests/monitoring.test.mjs +++ b/tests/monitoring.test.mjs @@ -112,13 +112,6 @@ test('noise: extensions, opaque cross-origin errors, ResizeObserver', () => { assert.equal(isNoise(new TypeError('x is undefined'), 'x is undefined', ' at f (https://fundermatch.org/assets/a.js:1:1)'), false); }); -// The page check above relies on Vite serving our bundle from /assets/. -test("the bundle is served from /assets/, as isNoise's page check assumes", () => { - const config = readFileSync(new URL('../vite.config.ts', import.meta.url), 'utf8'); - assert.match(config, /base: '\/'/); - assert.doesNotMatch(config, /assetsDir/); -}); - const shownLike = (msg) => { const e = new TypeError(msg); e.stack = 'global code@https://fundermatch.org/results:1:16';