diff --git a/src/main.ts b/src/main.ts index 72354f208..549cbeb74 100644 --- a/src/main.ts +++ b/src/main.ts @@ -317,7 +317,7 @@ Sentry.init({ /\.(?:toLowerCase|trim|indexOf|findIndex) is not a function/.test(msg) || /Maximum call stack size exceeded/.test(msg) || /out of memory/i.test(msg) - || /^\w{1,2} is not a function\. \(In '\w{1,2}\(/.test(msg) + || /^\w{1,2} is not a function/.test(msg) || /Cannot add property \w+, object is not extensible/.test(msg) || /^TypeError: Internal error$/.test(msg) || /NotSupportedError/.test(msg) @@ -327,6 +327,7 @@ Sentry.init({ || /^TypeError: NetworkError/.test(msg) || /Could not connect to the server/.test(msg) || /(?:Failed to fetch|Importing a module script failed|error loading) dynamically imported module/i.test(msg) + || (excType === 'SyntaxError' && /^Unexpected (?:token|keyword)/.test(msg)) || /^SyntaxError: Unexpected (?:token|keyword)/.test(msg) || /Invalid or unexpected token/.test(msg) || /^Operation timed out/.test(msg) diff --git a/tests/sentry-beforesend.test.mjs b/tests/sentry-beforesend.test.mjs index 5c9a762d8..24208d2cf 100644 --- a/tests/sentry-beforesend.test.mjs +++ b/tests/sentry-beforesend.test.mjs @@ -136,11 +136,15 @@ describe('empty-stack network/timeout errors are NOT suppressed', () => { 'Importing a module script failed.', 'Operation timed out', 'signal timed out', - 'SyntaxError: Unexpected token <', - 'SyntaxError: Unexpected keyword \'const\'', 'Invalid or unexpected token', ]; + // SyntaxErrors split by Sentry: type='SyntaxError', value='Unexpected token <' + const syntaxErrors = [ + ['Unexpected token <', 'SyntaxError'], + ['Unexpected keyword \'const\'', 'SyntaxError'], + ]; + for (const msg of networkErrors) { it(`lets through "${msg.slice(0, 60)}..." with empty stack`, () => { const event = makeEvent(msg, msg.startsWith('SyntaxError') ? 'SyntaxError' : 'TypeError', []); @@ -167,6 +171,25 @@ describe('empty-stack network/timeout errors are NOT suppressed', () => { assert.ok(result !== null, `"${msg}" with first-party stack should NOT be suppressed`); }); } + + // Sentry splits SyntaxError into type='SyntaxError' + value='Unexpected token <' + // The value field never contains the 'SyntaxError:' prefix. + for (const [value, type] of syntaxErrors) { + it(`suppresses SyntaxError (split: value="${value}") with third-party stack`, () => { + const event = makeEvent(value, type, [extensionFrame()]); + assert.equal(beforeSend(event), null); + }); + + it(`lets through SyntaxError (split: value="${value}") with empty stack`, () => { + const event = makeEvent(value, type, []); + assert.ok(beforeSend(event) !== null); + }); + + it(`lets through SyntaxError (split: value="${value}") with first-party stack`, () => { + const event = makeEvent(value, type, [firstPartyFrame()]); + assert.ok(beforeSend(event) !== null); + }); + } }); // ─── All ambiguous errors require confirmed third-party stack ──────────── @@ -184,6 +207,17 @@ describe('ambiguous runtime errors', () => { 'Element not found', ]; + // Chrome V8 emits "xy is not a function" without Safari's "(In 'xy(...')" suffix + it('suppresses Chrome-style "t is not a function" with third-party stack', () => { + const event = makeEvent('t is not a function', 'TypeError', [extensionFrame()]); + assert.equal(beforeSend(event), null); + }); + + it('suppresses Safari-style "t is not a function. (In \'t(..." with third-party stack', () => { + const event = makeEvent("t is not a function. (In 't(1,2)')", 'TypeError', [extensionFrame()]); + assert.equal(beforeSend(event), null); + }); + for (const msg of ambiguousErrors) { it(`lets through "${msg}" with empty stack (origin unknown)`, () => { const event = makeEvent(msg, 'TypeError', []);