diff --git a/.size-limit.js b/.size-limit.js index f6f7f342cb0c..ffa69d850947 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -47,7 +47,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration'), gzip: true, - limit: '75.1 KB', + limit: '75.2 KB', }, { name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags', diff --git a/packages/core/src/integrations/eventFilters.ts b/packages/core/src/integrations/eventFilters.ts index 087623367e28..ab935ec01321 100644 --- a/packages/core/src/integrations/eventFilters.ts +++ b/packages/core/src/integrations/eventFilters.ts @@ -209,13 +209,12 @@ function _getLastValidUrl(frames: StackFrame[] = []): string | null { function _getEventFilterUrl(event: Event): string | null { try { - let frames; - try { - // @ts-expect-error we only care about frames if the whole thing here is defined - frames = event.exception.values[0].stacktrace.frames; - } catch (e) { - // ignore - } + // If there are linked exceptions or exception aggregates we only want to match against the top frame of the "root" (the main exception) + // The root always comes last in linked exceptions + const rootException = [...(event.exception?.values ?? []).reverse()]?.find( + value => value.mechanism?.parent_id === undefined && value.stacktrace?.frames?.length, + ); + const frames = rootException?.stacktrace?.frames; return frames ? _getLastValidUrl(frames) : null; } catch (oO) { DEBUG_BUILD && logger.error(`Cannot extract url for event ${getEventDescription(event)}`); diff --git a/packages/core/test/lib/integrations/eventFilters.test.ts b/packages/core/test/lib/integrations/eventFilters.test.ts index b89f0f97a8e7..07936f6ad444 100644 --- a/packages/core/test/lib/integrations/eventFilters.test.ts +++ b/packages/core/test/lib/integrations/eventFilters.test.ts @@ -38,7 +38,6 @@ function createEventFiltersEventProcessor( dsn: PUBLIC_DSN, ...clientOptions, defaultIntegrations: false, - // eslint-disable-next-line deprecation/deprecation integrations: [integration], }), ); @@ -162,10 +161,91 @@ const EXCEPTION_EVENT_WITH_LINKED_ERRORS: Event = { { type: 'ReferenceError', value: '`tooManyTreats` is not defined', + stacktrace: { + frames: [{ filename: 'https://secondary-error.com/' }], + }, + }, + { + type: 'TypeError', + value: 'incorrect type given for parameter `chewToy`: Shoe', + stacktrace: { + frames: [{ filename: 'https://main-error.com/' }], + }, + }, + ], + }, +}; + +const EXCEPTION_EVENT_WITH_AGGREGATE_ERRORS: Event = { + exception: { + values: [ + { + type: 'ReferenceError', + value: '`tooManyTreats` is not defined', + stacktrace: { + frames: [{ filename: 'https://secondary-error.com/' }], + }, + mechanism: { + type: 'generic', + exception_id: 1, + parent_id: 0, + }, }, { type: 'TypeError', value: 'incorrect type given for parameter `chewToy`: Shoe', + stacktrace: { + frames: [{ filename: 'https://main-error.com/' }], + }, + mechanism: { + type: 'generic', + exception_id: 0, + }, + }, + ], + }, +}; + +const EXCEPTION_EVENT_WITH_LINKED_ERRORS_WITHOUT_STACKTRACE: Event = { + exception: { + values: [ + { + type: 'ReferenceError', + value: '`tooManyTreats` is not defined', + stacktrace: { + frames: [{ filename: 'https://main-error.com/' }], + }, + }, + { + type: 'TypeError', + value: 'incorrect type given for parameter `chewToy`: Shoe', + }, + ], + }, +}; + +const EXCEPTION_EVENT_WITH_AGGREGATE_ERRORS_WITHOUT_STACKTRACE: Event = { + exception: { + values: [ + { + type: 'ReferenceError', + value: '`tooManyTreats` is not defined', + stacktrace: { + frames: [{ filename: 'https://secondary-error.com/' }], + }, + mechanism: { + type: 'generic', + exception_id: 1, + parent_id: 0, + }, + }, + { + type: 'TypeError', + value: 'incorrect type given for parameter `chewToy`: Shoe', + mechanism: { + type: 'generic', + exception_id: 0, + }, }, ], }, @@ -624,6 +704,36 @@ describe.each([ }); expect(eventProcessor(MESSAGE_EVENT_WITH_NATIVE_LAST_FRAME, {})).toBe(null); }); + + it('should apply denyUrls to the "root" error of a linked exception', () => { + const eventProcessor = createEventFiltersEventProcessor(integrationFn, { + denyUrls: ['https://main-error.com'], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_LINKED_ERRORS, {})).toBe(null); + }); + + it('should apply denyUrls to the "root" error of an aggregate exception', () => { + const eventProcessor = createEventFiltersEventProcessor(integrationFn, { + denyUrls: ['https://main-error.com'], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_AGGREGATE_ERRORS, {})).toBe(null); + }); + + it('should apply allowUrls to the "most root" exception in the event if there are exceptions without stacktrace', () => { + const eventProcessor = createEventFiltersEventProcessor(integrationFn, { + allowUrls: ['https://some-error-that-is-not-main-error.com'], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_LINKED_ERRORS_WITHOUT_STACKTRACE, {})).toBe(null); + }); + + it('should not apply allowUrls to the event when the "root" exception of an aggregate error doesn\'t have a stacktrace', () => { + const eventProcessor = createEventFiltersEventProcessor(integrationFn, { + allowUrls: ['https://some-error-that-doesnt-match-anything.com'], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_AGGREGATE_ERRORS_WITHOUT_STACKTRACE, {})).toBe( + EXCEPTION_EVENT_WITH_AGGREGATE_ERRORS_WITHOUT_STACKTRACE, + ); + }); }); describe('useless errors', () => {