diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 6a2cf7133f8b..04d395b69534 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -1,13 +1,12 @@ -import { addGlobalEventProcessor, getCurrentHub } from '@sentry/hub'; -import { Event, Integration, StackFrame } from '@sentry/types'; +import { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; import { getEventDescription, isDebugBuild, isMatchingPattern, logger } from '@sentry/utils'; // "Script error." is hard coded into browsers for errors that it can't read. // this is the result of a script being pulled in from an external domain and CORS. const DEFAULT_IGNORE_ERRORS = [/^Script error\.?$/, /^Javascript error: Script error\.? on line 0$/]; -/** JSDoc */ -interface InboundFiltersOptions { +/** Options for the InboundFilters integration */ +export interface InboundFiltersOptions { allowUrls: Array; denyUrls: Array; ignoreErrors: Array; @@ -36,190 +35,171 @@ export class InboundFilters implements Integration { /** * @inheritDoc */ - public setupOnce(): void { + public setupOnce(addGlobalEventProcessor: (processor: EventProcessor) => void, getCurrentHub: () => Hub): void { addGlobalEventProcessor((event: Event) => { const hub = getCurrentHub(); - if (!hub) { - return event; - } - const self = hub.getIntegration(InboundFilters); - if (self) { - const client = hub.getClient(); - const clientOptions = client ? client.getOptions() : {}; - // This checks prevents most of the occurrences of the bug linked below: - // https://github.com/getsentry/sentry-javascript/issues/2622 - // The bug is caused by multiple SDK instances, where one is minified and one is using non-mangled code. - // Unfortunatelly we cannot fix it reliably (thus reserved property in rollup's terser config), - // as we cannot force people using multiple instances in their apps to sync SDK versions. - const options = typeof self._mergeOptions === 'function' ? self._mergeOptions(clientOptions) : {}; - if (typeof self._shouldDropEvent !== 'function') { - return event; + if (hub) { + const self = hub.getIntegration(InboundFilters); + if (self) { + const client = hub.getClient(); + const clientOptions = client ? client.getOptions() : {}; + const options = _mergeOptions(self._options, clientOptions); + return _shouldDropEvent(event, options) ? null : event; } - return self._shouldDropEvent(event, options) ? null : event; } return event; }); } +} - /** JSDoc */ - private _shouldDropEvent(event: Event, options: Partial): boolean { - if (this._isSentryError(event, options)) { - isDebugBuild() && - logger.warn(`Event dropped due to being internal Sentry Error.\nEvent: ${getEventDescription(event)}`); - return true; - } - if (this._isIgnoredError(event, options)) { - isDebugBuild() && - logger.warn( - `Event dropped due to being matched by \`ignoreErrors\` option.\nEvent: ${getEventDescription(event)}`, - ); - return true; - } - if (this._isDeniedUrl(event, options)) { - isDebugBuild() && - logger.warn( - `Event dropped due to being matched by \`denyUrls\` option.\nEvent: ${getEventDescription( - event, - )}.\nUrl: ${this._getEventFilterUrl(event)}`, - ); - return true; - } - if (!this._isAllowedUrl(event, options)) { - isDebugBuild() && - logger.warn( - `Event dropped due to not being matched by \`allowUrls\` option.\nEvent: ${getEventDescription( - event, - )}.\nUrl: ${this._getEventFilterUrl(event)}`, - ); - return true; - } - return false; - } - - /** JSDoc */ - private _isSentryError(event: Event, options: Partial): boolean { - if (!options.ignoreInternal) { - return false; - } +/** JSDoc */ +export function _mergeOptions( + internalOptions: Partial = {}, + clientOptions: Partial = {}, +): Partial { + return { + allowUrls: [ + // eslint-disable-next-line deprecation/deprecation + ...(internalOptions.whitelistUrls || []), + ...(internalOptions.allowUrls || []), + // eslint-disable-next-line deprecation/deprecation + ...(clientOptions.whitelistUrls || []), + ...(clientOptions.allowUrls || []), + ], + denyUrls: [ + // eslint-disable-next-line deprecation/deprecation + ...(internalOptions.blacklistUrls || []), + ...(internalOptions.denyUrls || []), + // eslint-disable-next-line deprecation/deprecation + ...(clientOptions.blacklistUrls || []), + ...(clientOptions.denyUrls || []), + ], + ignoreErrors: [ + ...(internalOptions.ignoreErrors || []), + ...(clientOptions.ignoreErrors || []), + ...DEFAULT_IGNORE_ERRORS, + ], + ignoreInternal: internalOptions.ignoreInternal !== undefined ? internalOptions.ignoreInternal : true, + }; +} - try { - // @ts-ignore can't be a sentry error if undefined - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return event.exception.values[0].type === 'SentryError'; - } catch (e) { - // ignore - } +/** JSDoc */ +export function _shouldDropEvent(event: Event, options: Partial): boolean { + if (options.ignoreInternal && _isSentryError(event)) { + isDebugBuild() && + logger.warn(`Event dropped due to being internal Sentry Error.\nEvent: ${getEventDescription(event)}`); + return true; + } + if (_isIgnoredError(event, options.ignoreErrors)) { + isDebugBuild() && + logger.warn( + `Event dropped due to being matched by \`ignoreErrors\` option.\nEvent: ${getEventDescription(event)}`, + ); + return true; + } + if (_isDeniedUrl(event, options.denyUrls)) { + isDebugBuild() && + logger.warn( + `Event dropped due to being matched by \`denyUrls\` option.\nEvent: ${getEventDescription( + event, + )}.\nUrl: ${_getEventFilterUrl(event)}`, + ); + return true; + } + if (!_isAllowedUrl(event, options.allowUrls)) { + isDebugBuild() && + logger.warn( + `Event dropped due to not being matched by \`allowUrls\` option.\nEvent: ${getEventDescription( + event, + )}.\nUrl: ${_getEventFilterUrl(event)}`, + ); + return true; + } + return false; +} +function _isIgnoredError(event: Event, ignoreErrors?: Array): boolean { + if (!ignoreErrors || !ignoreErrors.length) { return false; } - /** JSDoc */ - private _isIgnoredError(event: Event, options: Partial): boolean { - if (!options.ignoreErrors || !options.ignoreErrors.length) { - return false; - } + return _getPossibleEventMessages(event).some(message => + ignoreErrors.some(pattern => isMatchingPattern(message, pattern)), + ); +} - return this._getPossibleEventMessages(event).some(message => - // Not sure why TypeScript complains here... - (options.ignoreErrors as Array).some(pattern => isMatchingPattern(message, pattern)), - ); +function _isDeniedUrl(event: Event, denyUrls?: Array): boolean { + // TODO: Use Glob instead? + if (!denyUrls || !denyUrls.length) { + return false; } + const url = _getEventFilterUrl(event); + return !url ? false : denyUrls.some(pattern => isMatchingPattern(url, pattern)); +} - /** JSDoc */ - private _isDeniedUrl(event: Event, options: Partial): boolean { - // TODO: Use Glob instead? - if (!options.denyUrls || !options.denyUrls.length) { - return false; - } - const url = this._getEventFilterUrl(event); - return !url ? false : options.denyUrls.some(pattern => isMatchingPattern(url, pattern)); +function _isAllowedUrl(event: Event, allowUrls?: Array): boolean { + // TODO: Use Glob instead? + if (!allowUrls || !allowUrls.length) { + return true; } + const url = _getEventFilterUrl(event); + return !url ? true : allowUrls.some(pattern => isMatchingPattern(url, pattern)); +} - /** JSDoc */ - private _isAllowedUrl(event: Event, options: Partial): boolean { - // TODO: Use Glob instead? - if (!options.allowUrls || !options.allowUrls.length) { - return true; +function _getPossibleEventMessages(event: Event): string[] { + if (event.message) { + return [event.message]; + } + if (event.exception) { + try { + const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {}; + return [`${value}`, `${type}: ${value}`]; + } catch (oO) { + isDebugBuild() && logger.error(`Cannot extract message for event ${getEventDescription(event)}`); + return []; } - const url = this._getEventFilterUrl(event); - return !url ? true : options.allowUrls.some(pattern => isMatchingPattern(url, pattern)); } + return []; +} - /** JSDoc */ - private _mergeOptions(clientOptions: Partial = {}): Partial { - return { - allowUrls: [ - // eslint-disable-next-line deprecation/deprecation - ...(this._options.whitelistUrls || []), - ...(this._options.allowUrls || []), - // eslint-disable-next-line deprecation/deprecation - ...(clientOptions.whitelistUrls || []), - ...(clientOptions.allowUrls || []), - ], - denyUrls: [ - // eslint-disable-next-line deprecation/deprecation - ...(this._options.blacklistUrls || []), - ...(this._options.denyUrls || []), - // eslint-disable-next-line deprecation/deprecation - ...(clientOptions.blacklistUrls || []), - ...(clientOptions.denyUrls || []), - ], - ignoreErrors: [ - ...(this._options.ignoreErrors || []), - ...(clientOptions.ignoreErrors || []), - ...DEFAULT_IGNORE_ERRORS, - ], - ignoreInternal: typeof this._options.ignoreInternal !== 'undefined' ? this._options.ignoreInternal : true, - }; +function _isSentryError(event: Event): boolean { + try { + // @ts-ignore can't be a sentry error if undefined + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return event.exception.values[0].type === 'SentryError'; + } catch (e) { + // ignore } + return false; +} - /** JSDoc */ - private _getPossibleEventMessages(event: Event): string[] { - if (event.message) { - return [event.message]; - } - if (event.exception) { - try { - const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {}; - return [`${value}`, `${type}: ${value}`]; - } catch (oO) { - isDebugBuild() && logger.error(`Cannot extract message for event ${getEventDescription(event)}`); - return []; - } +function _getLastValidUrl(frames: StackFrame[] = []): string | null { + for (let i = frames.length - 1; i >= 0; i--) { + const frame = frames[i]; + + if (frame && frame.filename !== '' && frame.filename !== '[native code]') { + return frame.filename || null; } - return []; } - /** JSDoc */ - private _getLastValidUrl(frames: StackFrame[] = []): string | null { - for (let i = frames.length - 1; i >= 0; i--) { - const frame = frames[i]; + return null; +} - if (frame && frame.filename !== '' && frame.filename !== '[native code]') { - return frame.filename || null; - } +function _getEventFilterUrl(event: Event): string | null { + try { + if (event.stacktrace) { + return _getLastValidUrl(event.stacktrace.frames); } - - return null; - } - - /** JSDoc */ - private _getEventFilterUrl(event: Event): string | null { + let frames; try { - if (event.stacktrace) { - return this._getLastValidUrl(event.stacktrace.frames); - } - let frames; - try { - // @ts-ignore we only care about frames if the whole thing here is defined - frames = event.exception.values[0].stacktrace.frames; - } catch (e) { - // ignore - } - return frames ? this._getLastValidUrl(frames) : null; - } catch (oO) { - isDebugBuild() && logger.error(`Cannot extract url for event ${getEventDescription(event)}`); - return null; + // @ts-ignore we only care about frames if the whole thing here is defined + frames = event.exception.values[0].stacktrace.frames; + } catch (e) { + // ignore } + return frames ? _getLastValidUrl(frames) : null; + } catch (oO) { + isDebugBuild() && logger.error(`Cannot extract url for event ${getEventDescription(event)}`); + return null; } } diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts index f711f4725a0b..fd6530cd75e3 100644 --- a/packages/core/test/lib/integrations/inboundfilters.test.ts +++ b/packages/core/test/lib/integrations/inboundfilters.test.ts @@ -1,522 +1,329 @@ -import { InboundFilters } from '../../../src/integrations/inboundfilters'; - -let inboundFilters: any; - -describe('InboundFilters', () => { - beforeEach(() => { - inboundFilters = new InboundFilters(); - }); - - describe('shouldDropEvent', () => { - it('should drop when error is internal one', () => { - inboundFilters._isSentryError = () => true; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(true); - }); - - it('should drop when error is ignored', () => { - inboundFilters._isIgnoredError = () => true; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(true); - }); - - it('should drop when url is denied', () => { - inboundFilters._isDeniedUrl = () => true; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(true); - }); - - it('should drop when url is not allowed', () => { - inboundFilters._isAllowedUrl = () => false; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(true); - }); - - it('should drop when url is not denied, but also not allowed', () => { - inboundFilters._isDeniedUrl = () => false; - inboundFilters._isAllowedUrl = () => false; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(true); - }); - - it('should drop when url is denied and allowed at the same time', () => { - inboundFilters._isDeniedUrl = () => true; - inboundFilters._isAllowedUrl = () => true; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(true); - }); - - it('should not drop when url is not denied, but allowed', () => { - inboundFilters._isDeniedUrl = () => false; - inboundFilters._isAllowedUrl = () => true; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(false); - }); - - it('should not drop when any of checks dont match', () => { - inboundFilters._isIgnoredError = () => false; - inboundFilters._isDeniedUrl = () => false; - inboundFilters._isAllowedUrl = () => true; - expect(inboundFilters._shouldDropEvent({}, inboundFilters._mergeOptions())).toBe(false); - }); - }); - - describe('isSentryError', () => { - const messageEvent = { - message: 'captureMessage', - }; - const exceptionEvent = { - exception: { - values: [ - { - type: 'SyntaxError', - value: 'unidentified ? at line 1337', - }, - ], +import { EventProcessor } from '@sentry/types'; + +import { InboundFilters, InboundFiltersOptions } from '../../../src/integrations/inboundfilters'; + +/** + * Creates an instance of the InboundFilters integration and returns + * the event processor that the InboundFilters integration creates. + * + * To test the InboundFilters integration, call this function and assert on + * how the event processor handles an event. For example, if you set up the + * InboundFilters to filter out an SOME_EXCEPTION_EVENT. + * + * ``` + * // some options that cause SOME_EXCEPTION_EVENT to be filtered + * const eventProcessor = createInboundFiltersEventProcessor(options); + * + * expect(eventProcessor(SOME_EXCEPTION_EVENT)).toBe(null); + * ``` + * + * @param options options passed into the InboundFilters integration + * @param clientOptions options passed into the mock Sentry client + */ +function createInboundFiltersEventProcessor( + options: Partial = {}, + clientOptions: Partial = {}, +): EventProcessor { + const eventProcessors: EventProcessor[] = []; + const inboundFiltersInstance = new InboundFilters(options); + + function addGlobalEventProcessor(processor: EventProcessor): void { + eventProcessors.push(processor); + expect(eventProcessors).toHaveLength(1); + } + + function getCurrentHub(): any { + return { + getIntegration(_integration: any): any { + // pretend integration is enabled + return inboundFiltersInstance; }, - }; - const sentryEvent = { - exception: { - values: [ - { - type: 'SentryError', - value: 'something something server connection', - }, - ], + getClient(): any { + return { + getOptions: () => clientOptions, + }; }, }; + } + + inboundFiltersInstance.setupOnce(addGlobalEventProcessor, getCurrentHub); + return eventProcessors[0]; +} + +// Fixtures + +const MESSAGE_EVENT = { + message: 'captureMessage', +}; + +const MESSAGE_EVENT_2 = { + message: 'captureMessageSomething', +}; + +const MESSAGE_EVENT_WITH_STACKTRACE = { + message: 'wat', + stacktrace: { + // Frames are always in the reverse order, as this is how Sentry expect them to come. + // Frame that crashed is the last one, the one from awesome-analytics + frames: [ + { filename: 'https://our-side.com/js/bundle.js' }, + { filename: 'https://our-side.com/js/bundle.js' }, + { filename: 'https://awesome-analytics.io/some/file.js' }, + ], + }, +}; + +const MESSAGE_EVENT_WITH_ANON_LAST_FRAME = { + message: 'any', + stacktrace: { + frames: [ + { filename: 'https://our-side.com/js/bundle.js' }, + { filename: 'https://awesome-analytics.io/some/file.js' }, + { filename: '' }, + ], + }, +}; + +const MESSAGE_EVENT_WITH_NATIVE_LAST_FRAME = { + message: 'any', + stacktrace: { + frames: [ + { filename: 'https://our-side.com/js/bundle.js' }, + { filename: 'https://awesome-analytics.io/some/file.js' }, + { filename: '[native code]' }, + ], + }, +}; + +const EXCEPTION_EVENT = { + exception: { + values: [ + { + type: 'SyntaxError', + value: 'unidentified ? at line 1337', + }, + ], + }, +}; + +const EXCEPTION_EVENT_WITH_FRAMES = { + exception: { + values: [ + { + stacktrace: { + // Frames are always in the reverse order, as this is how Sentry expect them to come. + // Frame that crashed is the last one, the one from awesome-analytics + frames: [ + { filename: 'https://our-side.com/js/bundle.js' }, + { filename: 'https://our-side.com/js/bundle.js' }, + { filename: 'https://awesome-analytics.io/some/file.js' }, + ], + }, + }, + ], + }, +}; + +const SENTRY_EVENT = { + exception: { + values: [ + { + type: 'SentryError', + value: 'something something server connection', + }, + ], + }, +}; + +const SCRIPT_ERROR_EVENT = { + exception: { + values: [ + { + type: '[undefined]', + value: 'Script error.', + }, + ], + }, +}; + +const MALFORMED_EVENT = { + stacktrace: { + frames: undefined, + }, +}; +describe('InboundFilters', () => { + describe('_isSentryError', () => { it('should work as expected', () => { - expect(inboundFilters._isSentryError(messageEvent, inboundFilters._mergeOptions())).toBe(false); - expect(inboundFilters._isSentryError(exceptionEvent, inboundFilters._mergeOptions())).toBe(false); - expect(inboundFilters._isSentryError(sentryEvent, inboundFilters._mergeOptions())).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor(); + expect(eventProcessor(MESSAGE_EVENT)).toBe(MESSAGE_EVENT); + expect(eventProcessor(EXCEPTION_EVENT)).toBe(EXCEPTION_EVENT); + expect(eventProcessor(SENTRY_EVENT)).toBe(null); }); it('should be configurable', () => { - inboundFilters = new InboundFilters({ - ignoreInternal: false, - }); - expect(inboundFilters._isSentryError(messageEvent, inboundFilters._mergeOptions())).toBe(false); - expect(inboundFilters._isSentryError(exceptionEvent, inboundFilters._mergeOptions())).toBe(false); - expect(inboundFilters._isSentryError(sentryEvent, inboundFilters._mergeOptions())).toBe(false); + const eventProcessor = createInboundFiltersEventProcessor({ ignoreInternal: false }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(MESSAGE_EVENT); + expect(eventProcessor(EXCEPTION_EVENT)).toBe(EXCEPTION_EVENT); + expect(eventProcessor(SENTRY_EVENT)).toBe(SENTRY_EVENT); }); }); describe('ignoreErrors', () => { - const messageEvent = { - message: 'captureMessage', - }; - const exceptionEvent = { - exception: { - values: [ - { - type: 'SyntaxError', - value: 'unidentified ? at line 1337', - }, - ], - }, - }; - it('string filter with partial match', () => { - expect( - inboundFilters._isIgnoredError( - messageEvent, - inboundFilters._mergeOptions({ - ignoreErrors: ['capture'], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: ['capture'], + }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(null); }); it('string filter with exact match', () => { - expect( - inboundFilters._isIgnoredError( - messageEvent, - inboundFilters._mergeOptions({ - ignoreErrors: ['captureMessage'], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: ['captureMessage'], + }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(null); }); it('regexp filter with partial match', () => { - expect( - inboundFilters._isIgnoredError( - messageEvent, - inboundFilters._mergeOptions({ - ignoreErrors: [/capture/], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: [/capture/], + }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(null); }); it('regexp filter with exact match', () => { - expect( - inboundFilters._isIgnoredError( - messageEvent, - inboundFilters._mergeOptions({ - ignoreErrors: [/^captureMessage$/], - }), - ), - ).toBe(true); - expect( - inboundFilters._isIgnoredError( - { - message: 'captureMessageSomething', - }, - inboundFilters._mergeOptions({ - ignoreErrors: [/^captureMessage$/], - }), - ), - ).toBe(false); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: [/^captureMessage$/], + }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(null); + expect(eventProcessor(MESSAGE_EVENT_2)).toBe(MESSAGE_EVENT_2); }); - it('uses message when both, message and exception are available', () => { - expect( - inboundFilters._isIgnoredError( - { - ...exceptionEvent, - ...messageEvent, - }, - inboundFilters._mergeOptions({ - ignoreErrors: [/captureMessage/], - }), - ), - ).toBe(true); + it('prefers message when both message and exception are available', () => { + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: [/captureMessage/], + }); + const event = { + ...EXCEPTION_EVENT, + ...MESSAGE_EVENT, + }; + expect(eventProcessor(event)).toBe(null); }); it('can use multiple filters', () => { - expect( - inboundFilters._isIgnoredError( - messageEvent, - inboundFilters._mergeOptions({ - ignoreErrors: ['captureMessage', /SyntaxError/], - }), - ), - ).toBe(true); - expect( - inboundFilters._isIgnoredError( - exceptionEvent, - inboundFilters._mergeOptions({ - ignoreErrors: ['captureMessage', /SyntaxError/], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: ['captureMessage', /SyntaxError/], + }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(null); + expect(eventProcessor(EXCEPTION_EVENT)).toBe(null); }); it('uses default filters', () => { - expect( - inboundFilters._isIgnoredError( - { - exception: { - values: [ - { - type: '[undefined]', - value: 'Script error.', - }, - ], - }, - }, - inboundFilters._mergeOptions(), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor(); + expect(eventProcessor(SCRIPT_ERROR_EVENT)).toBe(null); }); describe('on exception', () => { - it('uses exceptions data when message is unavailable', () => { - expect( - inboundFilters._isIgnoredError( - exceptionEvent, - inboundFilters._mergeOptions({ - ignoreErrors: ['SyntaxError: unidentified ? at line 1337'], - }), - ), - ).toBe(true); + it('uses exception data when message is unavailable', () => { + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: ['SyntaxError: unidentified ? at line 1337'], + }); + expect(eventProcessor(EXCEPTION_EVENT)).toBe(null); }); it('can match on exception value', () => { - expect( - inboundFilters._isIgnoredError( - exceptionEvent, - inboundFilters._mergeOptions({ - ignoreErrors: [/unidentified \?/], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: [/unidentified \?/], + }); + expect(eventProcessor(EXCEPTION_EVENT)).toBe(null); }); it('can match on exception type', () => { - expect( - inboundFilters._isIgnoredError( - exceptionEvent, - inboundFilters._mergeOptions({ - ignoreErrors: [/^SyntaxError/], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + ignoreErrors: [/^SyntaxError/], + }); + expect(eventProcessor(EXCEPTION_EVENT)).toBe(null); }); }); }); describe('denyUrls/allowUrls', () => { - const messageEvent = { - message: 'wat', - stacktrace: { - // Frames are always in the reverse order, as this is how Sentry expect them to come. - // Frame that crashed is the last one, the one from awesome-analytics - frames: [ - { filename: 'https://our-side.com/js/bundle.js' }, - { filename: 'https://our-side.com/js/bundle.js' }, - { filename: 'https://awesome-analytics.io/some/file.js' }, - ], - }, - }; - const exceptionEvent = { - exception: { - values: [ - { - stacktrace: { - // Frames are always in the reverse order, as this is how Sentry expect them to come. - // Frame that crashed is the last one, the one from awesome-analytics - frames: [ - { filename: 'https://our-side.com/js/bundle.js' }, - { filename: 'https://our-side.com/js/bundle.js' }, - { filename: 'https://awesome-analytics.io/some/file.js' }, - ], - }, - }, - ], - }, - }; - it('should filter captured message based on its stack trace using string filter', () => { - expect( - inboundFilters._isDeniedUrl( - messageEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(true); - expect( - inboundFilters._isAllowedUrl( - messageEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(true); + const eventProcessorDeny = createInboundFiltersEventProcessor({ + denyUrls: ['https://awesome-analytics.io'], + }); + expect(eventProcessorDeny(MESSAGE_EVENT_WITH_STACKTRACE)).toBe(null); + }); + + it('should allow denyUrls to take precedence', () => { + const eventProcessorBoth = createInboundFiltersEventProcessor({ + allowUrls: ['https://awesome-analytics.io'], + denyUrls: ['https://awesome-analytics.io'], + }); + expect(eventProcessorBoth(MESSAGE_EVENT_WITH_STACKTRACE)).toBe(null); }); it('should filter captured message based on its stack trace using regexp filter', () => { - expect( - inboundFilters._isDeniedUrl( - messageEvent, - inboundFilters._mergeOptions({ - denyUrls: [/awesome-analytics\.io/], - }), - ), - ).toBe(true); - expect( - inboundFilters._isAllowedUrl( - messageEvent, - inboundFilters._mergeOptions({ - denyUrls: [/awesome-analytics\.io/], - }), - ), - ).toBe(true); + const eventProcessorDeny = createInboundFiltersEventProcessor({ + denyUrls: [/awesome-analytics\.io/], + }); + expect(eventProcessorDeny(MESSAGE_EVENT_WITH_STACKTRACE)).toBe(null); }); it('should not filter captured messages with no stacktraces', () => { - expect( - inboundFilters._isDeniedUrl( - { - message: 'any', - }, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(false); - expect( - inboundFilters._isAllowedUrl( - { - message: 'any', - }, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['https://awesome-analytics.io'], + }); + expect(eventProcessor(MESSAGE_EVENT)).toBe(MESSAGE_EVENT); }); it('should filter captured exception based on its stack trace using string filter', () => { - expect( - inboundFilters._isDeniedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(true); - expect( - inboundFilters._isAllowedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['https://awesome-analytics.io'], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_FRAMES)).toBe(null); }); - it('should filter captured exceptions based on its stack trace using regexp filter', () => { - expect( - inboundFilters._isDeniedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: [/awesome-analytics\.io/], - denyUrls: [/awesome-analytics\.io/], - }), - ), - ).toBe(true); - expect( - inboundFilters._isAllowedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: [/awesome-analytics\.io/], - denyUrls: [/awesome-analytics\.io/], - }), - ), - ).toBe(true); + it('should filter captured exception based on its stack trace using regexp filter', () => { + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: [/awesome-analytics\.io/], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_FRAMES)).toBe(null); }); - it('should not filter events that doesnt pass the test', () => { - expect( - inboundFilters._isDeniedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: ['some-other-domain.com'], - denyUrls: ['some-other-domain.com'], - }), - ), - ).toBe(false); - expect( - inboundFilters._isAllowedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: ['some-other-domain.com'], - denyUrls: ['some-other-domain.com'], - }), - ), - ).toBe(false); + it("should not filter events that don't match the filtered values", () => { + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['some-other-domain.com'], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_FRAMES)).toBe(EXCEPTION_EVENT_WITH_FRAMES); }); it('should be able to use multiple filters', () => { - expect( - inboundFilters._isDeniedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: ['some-other-domain.com', /awesome-analytics\.io/], - denyUrls: ['some-other-domain.com', /awesome-analytics\.io/], - }), - ), - ).toBe(true); - expect( - inboundFilters._isAllowedUrl( - exceptionEvent, - inboundFilters._mergeOptions({ - allowUrls: ['some-other-domain.com', /awesome-analytics\.io/], - denyUrls: ['some-other-domain.com', /awesome-analytics\.io/], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['some-other-domain.com', /awesome-analytics\.io/], + }); + expect(eventProcessor(EXCEPTION_EVENT_WITH_FRAMES)).toBe(null); }); - it('should not fail with malformed event event and default to false for isdeniedUrl and true for isallowedUrl', () => { - const malformedEvent = { - stacktrace: { - frames: undefined, - }, - }; - expect( - inboundFilters._isDeniedUrl( - malformedEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(false); - expect( - inboundFilters._isAllowedUrl( - malformedEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io'], - denyUrls: ['https://awesome-analytics.io'], - }), - ), - ).toBe(true); + it('should not fail with malformed event event', () => { + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['https://awesome-analytics.io'], + }); + expect(eventProcessor(MALFORMED_EVENT)).toBe(MALFORMED_EVENT); }); it('should search for script names when there is an anonymous callback at the last frame', () => { - const messageEvent = { - message: 'any', - stacktrace: { - frames: [ - { filename: 'https://our-side.com/js/bundle.js' }, - { filename: 'https://awesome-analytics.io/some/file.js' }, - { filename: '' }, - ], - }, - }; - - expect( - inboundFilters._isAllowedUrl( - messageEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io/some/file.js'], - }), - ), - ).toBe(true); - - expect( - inboundFilters._isDeniedUrl( - messageEvent, - inboundFilters._mergeOptions({ - denyUrls: ['https://awesome-analytics.io/some/file.js'], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['https://awesome-analytics.io/some/file.js'], + }); + expect(eventProcessor(MESSAGE_EVENT_WITH_ANON_LAST_FRAME)).toBe(null); }); it('should search for script names when the last frame is from native code', () => { - const messageEvent = { - message: 'any', - stacktrace: { - frames: [ - { filename: 'https://our-side.com/js/bundle.js' }, - { filename: 'https://awesome-analytics.io/some/file.js' }, - { filename: '[native code]' }, - ], - }, - }; - - expect( - inboundFilters._isAllowedUrl( - messageEvent, - inboundFilters._mergeOptions({ - allowUrls: ['https://awesome-analytics.io/some/file.js'], - }), - ), - ).toBe(true); - - expect( - inboundFilters._isDeniedUrl( - messageEvent, - inboundFilters._mergeOptions({ - denyUrls: ['https://awesome-analytics.io/some/file.js'], - }), - ), - ).toBe(true); + const eventProcessor = createInboundFiltersEventProcessor({ + denyUrls: ['https://awesome-analytics.io/some/file.js'], + }); + expect(eventProcessor(MESSAGE_EVENT_WITH_NATIVE_LAST_FRAME)).toBe(null); }); }); });