From a2283a0c03380f64ebdaaf84431f9f7967727410 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Thu, 9 Apr 2026 20:35:07 +0800 Subject: [PATCH] Clean up and improve non-gitea js error filter (#37148) (#37155) Backport #37148 by @silverwind 1. Filter out errors that contain `chrome-extension://` etc protocols 2. Extract filtering into its own function and test it 3. Fix the `window.config.assetUrlPrefix` mock, guaranteed to end with `/assets` 4. Remove useless `??` and `?.` for properties that always exist --- This PR was written with the help of Claude Opus 4.6 Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) --- web_src/js/modules/errors.test.ts | 17 ++++++++++++++++- web_src/js/modules/errors.ts | 21 ++++++++++++++------- web_src/js/vitest.setup.ts | 2 +- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/web_src/js/modules/errors.test.ts b/web_src/js/modules/errors.test.ts index 8ac0e262b2..efa114a88d 100644 --- a/web_src/js/modules/errors.test.ts +++ b/web_src/js/modules/errors.test.ts @@ -1,4 +1,19 @@ -import {showGlobalErrorMessage} from './errors.ts'; +import {isGiteaError, showGlobalErrorMessage} from './errors.ts'; + +test('isGiteaError', () => { + expect(isGiteaError('', '')).toBe(true); + expect(isGiteaError('moz-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('safari-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('safari-web-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('chrome-extension://abc/content.js', '')).toBe(false); + expect(isGiteaError('https://other-site.com/script.js', '')).toBe(false); + expect(isGiteaError('http://localhost:3000/some/page', '')).toBe(true); + expect(isGiteaError('http://localhost:3000/assets/js/index.abc123.js', '')).toBe(true); + expect(isGiteaError('', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false); + expect(isGiteaError('', `Error\n at https://other-site.com/script.js:1:1`)).toBe(false); + expect(isGiteaError('', `Error\n at http://localhost:3000/assets/js/index.abc123.js:1:1`)).toBe(true); + expect(isGiteaError('http://localhost:3000/assets/js/index.js', `Error\n at chrome-extension://abc/content.js:1:1`)).toBe(false); +}); test('showGlobalErrorMessage', () => { document.body.innerHTML = '
'; diff --git a/web_src/js/modules/errors.ts b/web_src/js/modules/errors.ts index 882da36977..ddda0ebe42 100644 --- a/web_src/js/modules/errors.ts +++ b/web_src/js/modules/errors.ts @@ -23,11 +23,19 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') { msgContainer.prepend(msgDiv); } +// Detect whether an error originated from Gitea's own scripts, not from +// browser extensions or other external scripts. +const extensionRe = /(chrome|moz|safari(-web)?)-extension:\/\//; +export function isGiteaError(filename: string, stack: string): boolean { + if (extensionRe.test(filename) || extensionRe.test(stack)) return false; + const assetBaseUrl = new URL(`${window.config.assetUrlPrefix}/`, window.location.origin).href; + if (filename && !filename.startsWith(assetBaseUrl) && !filename.startsWith(window.location.origin)) return false; + if (stack && !stack.includes(assetBaseUrl)) return false; + return true; +} + export function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) { const err = error ?? reason; - const assetBaseUrl = String(new URL(`${window.config?.assetUrlPrefix ?? '/assets'}/`, window.location.origin)); - const {runModeIsProd} = window.config ?? {}; - // `error` and `reason` are not guaranteed to be errors. If the value is falsy, it is likely a // non-critical event from the browser. We log them but don't show them to users. Examples: // - https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors @@ -35,12 +43,11 @@ export function processWindowErrorEvent({error, reason, message, type, filename, // - https://github.com/go-gitea/gitea/issues/20240 if (!err) { if (message) console.error(new Error(message)); - if (runModeIsProd) return; + if (window.config.runModeIsProd) return; } - // If the error stack trace does not include the base URL of our script assets, it likely came - // from a browser extension or inline script. Do not show such errors in production. - if (err instanceof Error && !err.stack?.includes(assetBaseUrl) && runModeIsProd) return; + // Filter out errors from browser extensions or other non-Gitea scripts. + if (!isGiteaError(filename ?? '', err?.stack ?? '')) return; let msg = err?.message ?? message; if (lineno) msg += ` (${filename} @ ${lineno}:${colno})`; diff --git a/web_src/js/vitest.setup.ts b/web_src/js/vitest.setup.ts index 5623075a27..a08325bcba 100644 --- a/web_src/js/vitest.setup.ts +++ b/web_src/js/vitest.setup.ts @@ -13,7 +13,7 @@ await import('./globals.ts'); window.config = { appUrl: 'http://localhost:3000/', appSubUrl: '', - assetUrlPrefix: '', + assetUrlPrefix: '/assets', sharedWorkerUri: '', runModeIsProd: true, customEmojis: {},