diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index d499c12d661b..b123afdc141b 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -76,7 +76,7 @@ jobs: with: list-files: json filters: | - browser_integration: dev-packages/browser-integration-tests/suites/** + browser_integration: dev-packages/browser-integration-tests/suites/**/test.ts - name: Detect flaky tests run: yarn test:detect-flaky diff --git a/MIGRATION.md b/MIGRATION.md index 0bbe719d524b..856df6f01f99 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -49,9 +49,14 @@ to access and mutate the current scope. ## General API Changes +- The minumum supported Node version for all the SDK packages is Node 14 (#10527) - Remove `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) - Remove deprecated `deepReadDirSync` export from `@sentry/node` (#10564) - Remove `_eventFromIncompleteOnError` usage (#10553) +- The `Transaction` integration in `@sentry/integrations` has been removed. There is no replacement API. (#10556) +- `extraErrorDataIntegration` now looks at + [`error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) by + default. # Deprecations in 7.x diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js index 4461826e0214..b5b85dd2a8e9 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js @@ -1,9 +1,9 @@ import * as Sentry from '@sentry/browser'; -import { ContextLines } from '@sentry/integrations'; +import { contextLinesIntegration } from '@sentry/integrations'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new ContextLines()], + integrations: [contextLinesIntegration()], }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js index 5d43b49e75fb..07bc4a5b351e 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { HttpClient } from '@sentry/integrations'; +import { httpClientIntegration } from '@sentry/integrations'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new HttpClient()], + integrations: [httpClientIntegration()], tracesSampleRate: 1, sendDefaultPii: true, }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js index 5d43b49e75fb..07bc4a5b351e 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { HttpClient } from '@sentry/integrations'; +import { httpClientIntegration } from '@sentry/integrations'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new HttpClient()], + integrations: [httpClientIntegration()], tracesSampleRate: 1, sendDefaultPii: true, }); diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts index 43c952b23594..9006936c4e60 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -1,4 +1,4 @@ -import * as Integrations from '@sentry/integrations'; +import { httpClientIntegration } from '@sentry/integrations'; import * as Sentry from '@sentry/node'; import '@sentry/tracing'; import express from 'express'; @@ -13,7 +13,7 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, includeLocalVariables: true, - integrations: [new Integrations.HttpClient()], + integrations: [httpClientIntegration()], debug: true, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, diff --git a/packages/browser/test/integration/common/init.js b/packages/browser/test/integration/common/init.js index 8215232aadcc..8f126bf175aa 100644 --- a/packages/browser/test/integration/common/init.js +++ b/packages/browser/test/integration/common/init.js @@ -22,7 +22,6 @@ var dsn = function initSDK() { Sentry.init({ dsn: dsn, - integrations: [new Sentry.Integrations.Dedupe()], attachStacktrace: true, ignoreErrors: ['ignoreErrorTest'], denyUrls: ['foo.js'], diff --git a/packages/browser/test/package/test-code.js b/packages/browser/test/package/test-code.js index 513009e945b5..72d0a494caa8 100644 --- a/packages/browser/test/package/test-code.js +++ b/packages/browser/test/package/test-code.js @@ -1,11 +1,9 @@ /* eslint-disable no-console */ const Sentry = require('../../build/npm/cjs/index.js'); -const Integrations = require('../../../integrations/build/npm/cjs/dedupe.js'); // Init Sentry.init({ dsn: 'https://completelyrandom@dsn.asdf/42', - integrations: [new Integrations.Dedupe()], beforeSend(_event) { console.log('Got an event'); return null; diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index 9bd6c45c8851..43419dcd32bd 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -1,12 +1,5 @@ -import { - captureException, - captureMessage, - convertIntegrationFnToClass, - defineIntegration, - getClient, - withScope, -} from '@sentry/core'; -import type { CaptureContext, Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { captureException, captureMessage, defineIntegration, getClient, withScope } from '@sentry/core'; +import type { CaptureContext, IntegrationFn } from '@sentry/types'; import { CONSOLE_LEVELS, GLOBAL_OBJ, @@ -45,19 +38,10 @@ const _captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => { }; }) satisfies IntegrationFn; -export const captureConsoleIntegration = defineIntegration(_captureConsoleIntegration); - /** * Send Console API calls as Sentry Events. - * @deprecated Use `captureConsoleIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const CaptureConsole = convertIntegrationFnToClass( - INTEGRATION_NAME, - captureConsoleIntegration, -) as IntegrationClass void }> & { - new (options?: { levels?: string[] }): Integration; -}; +export const captureConsoleIntegration = defineIntegration(_captureConsoleIntegration); function consoleHandler(args: unknown[], level: string): void { const captureContext: CaptureContext = { diff --git a/packages/integrations/src/contextlines.ts b/packages/integrations/src/contextlines.ts index f9835ad2f0f5..62c1b2728f1a 100644 --- a/packages/integrations/src/contextlines.ts +++ b/packages/integrations/src/contextlines.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; import { GLOBAL_OBJ, addContextToFrame, stripUrlQueryAndFragment } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; @@ -31,8 +31,6 @@ const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => { }; }) satisfies IntegrationFn; -export const contextLinesIntegration = defineIntegration(_contextLinesIntegration); - /** * Collects source context lines around the lines of stackframes pointing to JS embedded in * the current page's HTML. @@ -43,13 +41,8 @@ export const contextLinesIntegration = defineIntegration(_contextLinesIntegratio * * Use this integration if you have inline JS code in HTML pages that can't be accessed * by our backend (e.g. due to a login-protected page). - * - * @deprecated Use `contextLinesIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } -> & { new (options?: { frameContextLines?: number }): Integration }; +export const contextLinesIntegration = defineIntegration(_contextLinesIntegration); /** * Processes an event and adds context lines. diff --git a/packages/integrations/src/debug.ts b/packages/integrations/src/debug.ts index 7c23cba9ae57..1c1911cd03d8 100644 --- a/packages/integrations/src/debug.ts +++ b/packages/integrations/src/debug.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Event, EventHint, IntegrationFn } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; const INTEGRATION_NAME = 'Debug'; @@ -11,6 +11,10 @@ interface DebugOptions { debugger?: boolean; } +/** + * Integration to debug sent Sentry events. + * This integration should not be used in production. + */ const _debugIntegration = ((options: DebugOptions = {}) => { const _options = { debugger: false, @@ -50,19 +54,3 @@ const _debugIntegration = ((options: DebugOptions = {}) => { }) satisfies IntegrationFn; export const debugIntegration = defineIntegration(_debugIntegration); - -/** - * Integration to debug sent Sentry events. - * This integration should not be used in production. - * - * @deprecated Use `debugIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const Debug = convertIntegrationFnToClass(INTEGRATION_NAME, debugIntegration) as IntegrationClass< - Integration & { setup: (client: Client) => void } -> & { - new (options?: { - stringify?: boolean; - debugger?: boolean; - }): Integration; -}; diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index f230bc4f0621..63db131127c9 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Event, Exception, IntegrationFn, StackFrame } from '@sentry/types'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; @@ -33,16 +33,10 @@ const _dedupeIntegration = (() => { }; }) satisfies IntegrationFn; -export const dedupeIntegration = defineIntegration(_dedupeIntegration); - /** * Deduplication filter. - * @deprecated Use `dedupeIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } ->; +export const dedupeIntegration = defineIntegration(_dedupeIntegration); /** only exported for tests. */ export function _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean { diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index d1d6ae5f0b5d..21cc91008b7f 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -1,13 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { - Contexts, - Event, - EventHint, - ExtendedError, - Integration, - IntegrationClass, - IntegrationFn, -} from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Contexts, Event, EventHint, ExtendedError, IntegrationFn } from '@sentry/types'; import { addNonEnumerableProperty, isError, isPlainObject, logger, normalize } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; @@ -21,19 +13,18 @@ interface ExtraErrorDataOptions { depth: number; /** - * Whether to capture error causes. + * Whether to capture error causes. Defaults to true. * * More information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause */ captureErrorCause: boolean; } +/** + * Extract additional data for from original exceptions. + */ const _extraErrorDataIntegration = ((options: Partial = {}) => { - const depth = options.depth || 3; - - // TODO(v8): Flip the default for this option to true - const captureErrorCause = options.captureErrorCause || false; - + const { depth = 3, captureErrorCause = true } = options; return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -46,23 +37,6 @@ const _extraErrorDataIntegration = ((options: Partial = { export const extraErrorDataIntegration = defineIntegration(_extraErrorDataIntegration); -/** - * Extract additional data for from original exceptions. - * @deprecated Use `extraErrorDataIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const ExtraErrorData = convertIntegrationFnToClass( - INTEGRATION_NAME, - extraErrorDataIntegration, -) as IntegrationClass Event }> & { - new ( - options?: Partial<{ - depth: number; - captureErrorCause: boolean; - }>, - ): Integration; -}; - function _enhanceEventWithErrorData( event: Event, hint: EventHint = {}, diff --git a/packages/integrations/src/httpclient.ts b/packages/integrations/src/httpclient.ts index 0ae0b2d41184..276aadaf9baa 100644 --- a/packages/integrations/src/httpclient.ts +++ b/packages/integrations/src/httpclient.ts @@ -1,18 +1,5 @@ -import { - captureEvent, - convertIntegrationFnToClass, - defineIntegration, - getClient, - isSentryRequestUrl, -} from '@sentry/core'; -import type { - Client, - Event as SentryEvent, - Integration, - IntegrationClass, - IntegrationFn, - SentryWrappedXMLHttpRequest, -} from '@sentry/types'; +import { captureEvent, defineIntegration, getClient, isSentryRequestUrl } from '@sentry/core'; +import type { Client, Event as SentryEvent, IntegrationFn, SentryWrappedXMLHttpRequest } from '@sentry/types'; import { GLOBAL_OBJ, SENTRY_XHR_DATA_KEY, @@ -69,21 +56,10 @@ const _httpClientIntegration = ((options: Partial = {}) => { }; }) satisfies IntegrationFn; -export const httpClientIntegration = defineIntegration(_httpClientIntegration); - /** * Create events for failed client side HTTP requests. - * @deprecated Use `httpClientIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const HttpClient = convertIntegrationFnToClass(INTEGRATION_NAME, httpClientIntegration) as IntegrationClass< - Integration & { setup: (client: Client) => void } -> & { - new (options?: { - failedRequestStatusCodes: HttpStatusCodeRange[]; - failedRequestTargets: HttpRequestTarget[]; - }): Integration; -}; +export const httpClientIntegration = defineIntegration(_httpClientIntegration); /** * Interceptor function for fetch requests diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index a73b14257ed7..a2c1fb48920f 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -1,11 +1,9 @@ -/* eslint-disable deprecation/deprecation */ -export { CaptureConsole, captureConsoleIntegration } from './captureconsole'; -export { Debug, debugIntegration } from './debug'; -export { Dedupe, dedupeIntegration } from './dedupe'; -export { ExtraErrorData, extraErrorDataIntegration } from './extraerrordata'; -export { ReportingObserver, reportingObserverIntegration } from './reportingobserver'; -export { RewriteFrames, rewriteFramesIntegration } from './rewriteframes'; -export { SessionTiming, sessionTimingIntegration } from './sessiontiming'; -export { Transaction } from './transaction'; -export { HttpClient, httpClientIntegration } from './httpclient'; -export { ContextLines, contextLinesIntegration } from './contextlines'; +export { captureConsoleIntegration } from './captureconsole'; +export { debugIntegration } from './debug'; +export { dedupeIntegration } from './dedupe'; +export { extraErrorDataIntegration } from './extraerrordata'; +export { reportingObserverIntegration } from './reportingobserver'; +export { rewriteFramesIntegration } from './rewriteframes'; +export { sessionTimingIntegration } from './sessiontiming'; +export { httpClientIntegration } from './httpclient'; +export { contextLinesIntegration } from './contextlines'; diff --git a/packages/integrations/src/reportingobserver.ts b/packages/integrations/src/reportingobserver.ts index b22133d771fe..12a4039542b9 100644 --- a/packages/integrations/src/reportingobserver.ts +++ b/packages/integrations/src/reportingobserver.ts @@ -1,5 +1,5 @@ -import { captureMessage, convertIntegrationFnToClass, defineIntegration, getClient, withScope } from '@sentry/core'; -import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { captureMessage, defineIntegration, getClient, withScope } from '@sentry/core'; +import type { Client, IntegrationFn } from '@sentry/types'; import { GLOBAL_OBJ, supportsReportingObserver } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; @@ -115,18 +115,7 @@ const _reportingObserverIntegration = ((options: ReportingObserverOptions = {}) }; }) satisfies IntegrationFn; -export const reportingObserverIntegration = defineIntegration(_reportingObserverIntegration); - /** * Reporting API integration - https://w3c.github.io/reporting/ - * @deprecated Use `reportingObserverIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const ReportingObserver = convertIntegrationFnToClass( - INTEGRATION_NAME, - reportingObserverIntegration, -) as IntegrationClass void }> & { - new (options?: { - types?: ReportTypes[]; - }): Integration; -}; +export const reportingObserverIntegration = defineIntegration(_reportingObserverIntegration); diff --git a/packages/integrations/src/rewriteframes.ts b/packages/integrations/src/rewriteframes.ts index 8d6441649e3d..12183a3192ae 100644 --- a/packages/integrations/src/rewriteframes.ts +++ b/packages/integrations/src/rewriteframes.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame, Stacktrace } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Event, IntegrationFn, StackFrame, Stacktrace } from '@sentry/types'; import { basename, relative } from '@sentry/utils'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -85,16 +85,7 @@ const _rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { }; }) satisfies IntegrationFn; -export const rewriteFramesIntegration = defineIntegration(_rewriteFramesIntegration); - /** * Rewrite event frames paths. - * @deprecated Use `rewriteFramesIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const RewriteFrames = convertIntegrationFnToClass( - INTEGRATION_NAME, - rewriteFramesIntegration, -) as IntegrationClass Event }> & { - new (options?: { root?: string; prefix?: string; iteratee?: StackFrameIteratee }): Integration; -}; +export const rewriteFramesIntegration = defineIntegration(_rewriteFramesIntegration); diff --git a/packages/integrations/src/sessiontiming.ts b/packages/integrations/src/sessiontiming.ts index 81ef3f2627be..2c79dd5cd69a 100644 --- a/packages/integrations/src/sessiontiming.ts +++ b/packages/integrations/src/sessiontiming.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; const INTEGRATION_NAME = 'SessionTiming'; @@ -26,14 +26,8 @@ const _sessionTimingIntegration = (() => { }; }) satisfies IntegrationFn; -export const sessionTimingIntegration = defineIntegration(_sessionTimingIntegration); - /** - * This function adds duration since Sentry was initialized till the time event was sent. - * @deprecated Use `sessionTimingIntegration()` instead. + * This function adds duration since the sessionTimingIntegration was initialized + * till the time event was sent. */ -// eslint-disable-next-line deprecation/deprecation -export const SessionTiming = convertIntegrationFnToClass( - INTEGRATION_NAME, - sessionTimingIntegration, -) as IntegrationClass Event }>; +export const sessionTimingIntegration = defineIntegration(_sessionTimingIntegration); diff --git a/packages/integrations/src/transaction.ts b/packages/integrations/src/transaction.ts deleted file mode 100644 index 5d6d0cd595e9..000000000000 --- a/packages/integrations/src/transaction.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; - -const INTEGRATION_NAME = 'Transaction'; - -const transactionIntegration = (() => { - return { - name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function - processEvent(event) { - const frames = _getFramesFromEvent(event); - - // use for loop so we don't have to reverse whole frames array - for (let i = frames.length - 1; i >= 0; i--) { - const frame = frames[i]; - - if (frame.in_app === true) { - event.transaction = _getTransaction(frame); - break; - } - } - - return event; - }, - }; -}) satisfies IntegrationFn; - -/** - * Add node transaction to the event. - * @deprecated This integration will be removed in v8. - */ -// eslint-disable-next-line deprecation/deprecation -export const Transaction = convertIntegrationFnToClass(INTEGRATION_NAME, transactionIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } ->; - -function _getFramesFromEvent(event: Event): StackFrame[] { - const exception = event.exception && event.exception.values && event.exception.values[0]; - return (exception && exception.stacktrace && exception.stacktrace.frames) || []; -} - -function _getTransaction(frame: StackFrame): string { - return frame.module || frame.function ? `${frame.module || '?'}/${frame.function || '?'}` : ''; -} diff --git a/packages/integrations/test/captureconsole.test.ts b/packages/integrations/test/captureconsole.test.ts index 8452f2fe6e27..896c4b590181 100644 --- a/packages/integrations/test/captureconsole.test.ts +++ b/packages/integrations/test/captureconsole.test.ts @@ -10,8 +10,7 @@ import { resetInstrumentationHandlers, } from '@sentry/utils'; -import type { captureConsoleIntegration } from '../src/captureconsole'; -import { CaptureConsole } from '../src/captureconsole'; +import { captureConsoleIntegration } from '../src/captureconsole'; const mockConsole: { [key in ConsoleLevel]: jest.Mock } = { debug: jest.fn(), @@ -23,11 +22,6 @@ const mockConsole: { [key in ConsoleLevel]: jest.Mock } = { trace: jest.fn(), }; -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new CaptureConsole(...args); -} - describe('CaptureConsole setup', () => { // Ensure we've initialized the instrumentation so we can get the original one addConsoleInstrumentationHandler(() => {}); @@ -71,8 +65,8 @@ describe('CaptureConsole setup', () => { describe('monkeypatching', () => { it('should patch user-configured console levels', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log', 'warn'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log', 'warn'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.error('msg 1'); GLOBAL_OBJ.console.log('msg 2'); @@ -82,8 +76,8 @@ describe('CaptureConsole setup', () => { }); it('should fall back to default console levels if none are provided', () => { - const captureConsoleIntegration = getIntegration(); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration(); + captureConsole.setup?.(mockClient); // Assert has a special handling (['debug', 'info', 'warn', 'error', 'log', 'trace'] as const).forEach(key => { @@ -96,8 +90,8 @@ describe('CaptureConsole setup', () => { }); it('should not wrap any functions with an empty levels option', () => { - const captureConsoleIntegration = getIntegration({ levels: [] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: [] }); + captureConsole.setup?.(mockClient); CONSOLE_LEVELS.forEach(key => { GLOBAL_OBJ.console[key]('msg'); @@ -112,9 +106,9 @@ describe('CaptureConsole setup', () => { // @ts-expect-error remove console delete GLOBAL_OBJ.console; - const captureConsoleIntegration = getIntegration(); + const captureConsole = captureConsoleIntegration(); expect(() => { - captureConsoleIntegration.setup(mockClient); + captureConsole.setup?.(mockClient); }).not.toThrow(); // reinstate initial console @@ -122,8 +116,8 @@ describe('CaptureConsole setup', () => { }); it('should send empty arguments as extra data', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.log(); @@ -132,8 +126,8 @@ describe('CaptureConsole setup', () => { }); it('should add an event processor that sets the `logger` field of events', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); // call a wrapped function GLOBAL_OBJ.console.log('some message'); @@ -148,8 +142,8 @@ describe('CaptureConsole setup', () => { }); it('should capture message on a failed assertion', () => { - const captureConsoleIntegration = getIntegration({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['assert'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 3); @@ -162,8 +156,8 @@ describe('CaptureConsole setup', () => { }); it('should capture correct message on a failed assertion with message', () => { - const captureConsoleIntegration = getIntegration({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['assert'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 3, 'expression is false'); @@ -176,15 +170,15 @@ describe('CaptureConsole setup', () => { }); it('should not capture message on a successful assertion', () => { - const captureConsoleIntegration = getIntegration({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['assert'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 2); }); it('should capture exception when console logs an error object with level set to "error"', () => { - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); @@ -197,8 +191,8 @@ describe('CaptureConsole setup', () => { }); it('should capture exception on `console.error` when no levels are provided in constructor', () => { - const captureConsoleIntegration = getIntegration(); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration(); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); @@ -211,8 +205,8 @@ describe('CaptureConsole setup', () => { }); it('should capture exception when console logs an error object in any of the args when level set to "error"', () => { - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error('Something went wrong', someError); @@ -225,8 +219,8 @@ describe('CaptureConsole setup', () => { }); it('should capture message on `console.log` when no levels are provided in constructor', () => { - const captureConsoleIntegration = getIntegration(); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration(); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.error('some message'); @@ -238,8 +232,8 @@ describe('CaptureConsole setup', () => { }); it('should capture message when console logs a non-error object with level set to "error"', () => { - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.error('some non-error message'); @@ -252,8 +246,8 @@ describe('CaptureConsole setup', () => { }); it('should capture a message for non-error log levels', () => { - const captureConsoleIntegration = getIntegration({ levels: ['info'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['info'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.info('some message'); @@ -270,8 +264,8 @@ describe('CaptureConsole setup', () => { const mockConsoleLog = jest.fn(); GLOBAL_OBJ.console.log = mockConsoleLog; - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.log('some message 1', 'some message 2'); @@ -283,17 +277,17 @@ describe('CaptureConsole setup', () => { }); it('should not wrap any levels that are not members of console', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log', 'someNonExistingLevel', 'error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log', 'someNonExistingLevel', 'error'] }); + captureConsole.setup?.(mockClient); // The provided level should not be created expect((GLOBAL_OBJ.console as any)['someNonExistingLevel']).toBeUndefined(); }); it('should wrap the console when the client does not have a registered captureconsole integration, but not capture any messages', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log', 'error'] }); + const captureConsole = captureConsoleIntegration({ levels: ['log', 'error'] }); // when `setup` is not called on the current client, it will not trigger - captureConsoleIntegration.setup({} as Client); + captureConsole.setup?.({} as Client); // Should not capture messages GLOBAL_OBJ.console.log('some message'); @@ -303,8 +297,8 @@ describe('CaptureConsole setup', () => { it("should not crash when the original console methods don't exist at time of invocation", () => { originalConsoleMethods.log = undefined; - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); expect(() => { GLOBAL_OBJ.console.log('some message'); @@ -314,8 +308,8 @@ describe('CaptureConsole setup', () => { it("marks captured exception's mechanism as unhandled", () => { // const addExceptionMechanismSpy = jest.spyOn(utils, 'addExceptionMechanism'); - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); diff --git a/packages/integrations/test/debug.test.ts b/packages/integrations/test/debug.test.ts index b0d922b6de9f..5d0f90ba2074 100644 --- a/packages/integrations/test/debug.test.ts +++ b/packages/integrations/test/debug.test.ts @@ -1,18 +1,12 @@ -import type { Client, Event, EventHint, Integration } from '@sentry/types'; +import type { Client, Event, EventHint } from '@sentry/types'; -import type { debugIntegration } from '../src/debug'; -import { Debug } from '../src/debug'; +import { debugIntegration } from '../src/debug'; -interface IntegrationWithSetup extends Integration { - setup: (client: Client) => void; -} - -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new Debug(...args); -} - -function testEventLogged(integration: IntegrationWithSetup, testEvent?: Event, testEventHint?: EventHint) { +function testEventLogged( + integration: ReturnType, + testEvent?: Event, + testEventHint?: EventHint, +) { const callbacks: ((event: Event, hint?: EventHint) => void)[] = []; const client: Client = { @@ -22,7 +16,7 @@ function testEventLogged(integration: IntegrationWithSetup, testEvent?: Event, t }, } as Client; - integration.setup(client); + integration.setup?.(client); expect(callbacks.length).toEqual(1); @@ -48,22 +42,22 @@ describe('Debug integration setup should register an event processor that', () = }); it('logs an event', () => { - const debugIntegration = getIntegration(); + const debug = debugIntegration(); const testEvent = { event_id: 'some event' }; - testEventLogged(debugIntegration, testEvent); + testEventLogged(debug, testEvent); expect(mockConsoleLog).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toBeCalledWith(testEvent); }); it('logs an event hint if available', () => { - const debugIntegration = getIntegration(); + const debug = debugIntegration(); const testEvent = { event_id: 'some event' }; const testEventHint = { event_id: 'some event hint' }; - testEventLogged(debugIntegration, testEvent, testEventHint); + testEventLogged(debug, testEvent, testEventHint); expect(mockConsoleLog).toHaveBeenCalledTimes(2); expect(mockConsoleLog).toBeCalledWith(testEvent); @@ -71,22 +65,22 @@ describe('Debug integration setup should register an event processor that', () = }); it('logs events in stringified format when `stringify` option was set', () => { - const debugIntegration = getIntegration({ stringify: true }); + const debug = debugIntegration({ stringify: true }); const testEvent = { event_id: 'some event' }; - testEventLogged(debugIntegration, testEvent); + testEventLogged(debug, testEvent); expect(mockConsoleLog).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toBeCalledWith(JSON.stringify(testEvent, null, 2)); }); it('logs event hints in stringified format when `stringify` option was set', () => { - const debugIntegration = getIntegration({ stringify: true }); + const debug = debugIntegration({ stringify: true }); const testEvent = { event_id: 'some event' }; const testEventHint = { event_id: 'some event hint' }; - testEventLogged(debugIntegration, testEvent, testEventHint); + testEventLogged(debug, testEvent, testEventHint); expect(mockConsoleLog).toHaveBeenCalledTimes(2); expect(mockConsoleLog).toBeCalledWith(JSON.stringify(testEventHint, null, 2)); diff --git a/packages/integrations/test/dedupe.test.ts b/packages/integrations/test/dedupe.test.ts index 65bf7a5bbfb9..e3b3e4bc8a48 100644 --- a/packages/integrations/test/dedupe.test.ts +++ b/packages/integrations/test/dedupe.test.ts @@ -1,7 +1,6 @@ import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; -import type { dedupeIntegration } from '../src/dedupe'; -import { Dedupe, _shouldDropEvent } from '../src/dedupe'; +import { _shouldDropEvent, dedupeIntegration } from '../src/dedupe'; type EventWithException = SentryEvent & { exception: { @@ -15,11 +14,6 @@ function clone(data: T): T { return JSON.parse(JSON.stringify(data)); } -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new Dedupe(...args); -} - const messageEvent: EventWithException = { fingerprint: ['MrSnuffles'], message: 'PickleRick', @@ -183,27 +177,31 @@ describe('Dedupe', () => { describe('processEvent', () => { it('ignores consecutive errors', () => { - const integration = getIntegration(); + const integration = dedupeIntegration(); - expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).not.toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); }); it('ignores transactions between errors', () => { - const integration = getIntegration(); + const integration = dedupeIntegration(); - expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).not.toBeNull(); expect( - integration.processEvent({ - event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', - message: 'someMessage', - transaction: 'wat', - type: 'transaction', - }), + integration.processEvent?.( + { + event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', + message: 'someMessage', + transaction: 'wat', + type: 'transaction', + }, + {}, + {} as any, + ), ).not.toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); }); }); }); diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index b53b98885357..0a7bf19dd47d 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -1,14 +1,8 @@ import type { Event as SentryEvent, ExtendedError } from '@sentry/types'; -import type { extraErrorDataIntegration } from '../src/extraerrordata'; -import { ExtraErrorData } from '../src/extraerrordata'; +import { extraErrorDataIntegration } from '../src/extraerrordata'; -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new ExtraErrorData(...args); -} - -const extraErrorData = getIntegration(); +const extraErrorData = extraErrorDataIntegration(); let event: SentryEvent; describe('ExtraErrorData()', () => { @@ -21,9 +15,13 @@ describe('ExtraErrorData()', () => { error.baz = 42; error.foo = 'bar'; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -37,9 +35,13 @@ describe('ExtraErrorData()', () => { const error = new TypeError('foo') as ExtendedError; error.cause = new SyntaxError('bar'); - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -58,9 +60,13 @@ describe('ExtraErrorData()', () => { }, }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -82,9 +88,13 @@ describe('ExtraErrorData()', () => { const error = new TypeError('foo') as ExtendedError; error.baz = 42; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -97,24 +107,32 @@ describe('ExtraErrorData()', () => { it('should return event if originalException is not an Error object', () => { const error = 'error message, not object'; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent).toEqual(event); }); it('should return event if there is no SentryEventHint', () => { - const enhancedEvent = extraErrorData.processEvent(event, {}); + const enhancedEvent = extraErrorData.processEvent?.(event, {}, {} as any); expect(enhancedEvent).toEqual(event); }); it('should return event if there is no originalException', () => { - const enhancedEvent = extraErrorData.processEvent(event, { - // @ts-expect-error Allow event to have extra properties - notOriginalException: 'fooled you', - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + // @ts-expect-error Allow event to have extra properties + notOriginalException: 'fooled you', + }, + {} as any, + ); expect(enhancedEvent).toEqual(event); }); @@ -130,9 +148,13 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -153,9 +175,13 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -173,9 +199,13 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -185,21 +215,25 @@ describe('ExtraErrorData()', () => { }); }); - it('captures Error causes when captureErrorCause = true', () => { + it('captures Error causes when captureErrorCause = true (default)', () => { // Error.cause is only available from node 16 upwards const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); if (nodeMajorVersion < 16) { return; } - const extraErrorDataWithCauseCapture = getIntegration({ captureErrorCause: true }); + const extraErrorDataWithCauseCapture = extraErrorDataIntegration(); // @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; - const enhancedEvent = extraErrorDataWithCauseCapture.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorDataWithCauseCapture.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ Error: { @@ -217,14 +251,18 @@ describe('ExtraErrorData()', () => { return; } - const extraErrorDataWithoutCauseCapture = getIntegration(); + const extraErrorDataWithoutCauseCapture = extraErrorDataIntegration({ captureErrorCause: false }); // @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; - const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).not.toEqual({ Error: { diff --git a/packages/integrations/test/reportingobserver.test.ts b/packages/integrations/test/reportingobserver.test.ts index c699b0d7f8dc..8c8088770973 100644 --- a/packages/integrations/test/reportingobserver.test.ts +++ b/packages/integrations/test/reportingobserver.test.ts @@ -1,8 +1,7 @@ import * as SentryCore from '@sentry/core'; -import type { Client, Hub } from '@sentry/types'; +import type { Client } from '@sentry/types'; -import type { reportingObserverIntegration } from '../src/reportingobserver'; -import { ReportingObserver } from '../src/reportingobserver'; +import { reportingObserverIntegration } from '../src/reportingobserver'; const mockScope = { setExtra: jest.fn(), @@ -14,8 +13,6 @@ const withScope = jest.fn(callback => { const captureMessage = jest.fn(); -const mockHub = {} as unknown as Hub; - const mockReportingObserverConstructor = jest.fn(); const mockObserve = jest.fn(); @@ -27,11 +24,6 @@ class MockReportingObserver { } } -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new ReportingObserver(...args); -} - describe('ReportingObserver', () => { let mockClient: Client; @@ -55,13 +47,10 @@ describe('ReportingObserver', () => { // Act like ReportingObserver is unavailable delete (global as any).ReportingObserver; - const reportingObserverIntegration = getIntegration(); + const reportingObserver = reportingObserverIntegration(); expect(() => { - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); + reportingObserver.setupOnce(); }).not.toThrow(); expect(mockReportingObserverConstructor).not.toHaveBeenCalled(); @@ -69,12 +58,9 @@ describe('ReportingObserver', () => { }); it('should use default report types', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -84,12 +70,9 @@ describe('ReportingObserver', () => { }); it('should use user-provided report types', () => { - const reportingObserverIntegration = getIntegration({ types: ['crash'] }); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration({ types: ['crash'] }); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -99,12 +82,9 @@ describe('ReportingObserver', () => { }); it('should use `buffered` option', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -114,12 +94,9 @@ describe('ReportingObserver', () => { }); it('should call `observe` function', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); expect(mockObserve).toHaveBeenCalledTimes(1); }); @@ -127,11 +104,8 @@ describe('ReportingObserver', () => { describe('handler', () => { it('should abort gracefully and not do anything when integration is not installed', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); // without calling setup, the integration is not registered const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -144,12 +118,9 @@ describe('ReportingObserver', () => { }); it('should capture messages', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([ @@ -161,12 +132,9 @@ describe('ReportingObserver', () => { }); it('should set extra including the url of a report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([ @@ -179,12 +147,9 @@ describe('ReportingObserver', () => { }); it('should set extra including the report body if available', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report1 = { type: 'crash', url: 'some url 1', body: { crashId: 'id1' } } as const; @@ -197,12 +162,9 @@ describe('ReportingObserver', () => { }); it('should not set extra report body extra when no body is set', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([{ type: 'crash', url: 'some url' }]); @@ -211,12 +173,9 @@ describe('ReportingObserver', () => { }); it('should capture report details from body on crash report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -232,12 +191,9 @@ describe('ReportingObserver', () => { }); it('should capture report message from body on deprecation report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -252,12 +208,9 @@ describe('ReportingObserver', () => { }); it('should capture report message from body on intervention report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -272,12 +225,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body is available', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -291,12 +241,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body details are available for crash report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { type: 'crash', url: 'some url', body: { crashId: '', reason: '' } } as const; @@ -307,12 +254,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body message is available for deprecation report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -327,12 +271,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body message is available for intervention report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { diff --git a/packages/integrations/test/rewriteframes.test.ts b/packages/integrations/test/rewriteframes.test.ts index 7dc051c478cb..d13e88318b77 100644 --- a/packages/integrations/test/rewriteframes.test.ts +++ b/packages/integrations/test/rewriteframes.test.ts @@ -1,18 +1,8 @@ -import type { Event, Integration, StackFrame } from '@sentry/types'; +import type { Event, StackFrame } from '@sentry/types'; -import type { rewriteFramesIntegration } from '../src/rewriteframes'; -import { RewriteFrames } from '../src/rewriteframes'; +import { rewriteFramesIntegration } from '../src/rewriteframes'; -interface IntegrationWithProcessEvent extends Integration { - processEvent(event: Event): Event; -} - -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new RewriteFrames(...args); -} - -let rewriteFrames: IntegrationWithProcessEvent; +let rewriteFrames: ReturnType; let exceptionEvent: Event; let exceptionWithoutStackTrace: Event; let windowsExceptionEvent: Event; @@ -108,11 +98,11 @@ describe('RewriteFrames', () => { describe('default iteratee appends basename to `app:///` if frame starts with `/`', () => { beforeEach(() => { - rewriteFrames = getIntegration(); + rewriteFrames = rewriteFramesIntegration(); }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); @@ -120,20 +110,20 @@ describe('RewriteFrames', () => { it('ignore exception without StackTrace', () => { // @ts-expect-error Validates that the Stacktrace does not exist before validating the test. expect(exceptionWithoutStackTrace.exception?.values[0].stacktrace).toEqual(undefined); - const event = rewriteFrames.processEvent(exceptionWithoutStackTrace); + const event = rewriteFrames.processEvent?.(exceptionWithoutStackTrace, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace).toEqual(undefined); }); }); describe('default iteratee prepends custom prefix to basename if frame starts with `/`', () => { beforeEach(() => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ prefix: 'foobar/', }); }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('foobar/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('foobar/file2.js'); }); @@ -141,29 +131,29 @@ describe('RewriteFrames', () => { describe('default iteratee appends basename to `app:///` if frame starts with Windows path prefix', () => { beforeEach(() => { - rewriteFrames = getIntegration(); + rewriteFrames = rewriteFramesIntegration(); }); it('transforms windowsExceptionEvent frames (C:\\)', () => { - const event = rewriteFrames.processEvent(windowsExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with lower-case prefix (c:\\)', () => { - const event = rewriteFrames.processEvent(windowsLowerCaseExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsLowerCaseExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with no prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithoutPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithoutPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with backslash prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithBackslashPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithBackslashPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); @@ -171,37 +161,37 @@ describe('RewriteFrames', () => { describe('can use custom root to perform `relative` on filepaths', () => { beforeEach(() => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ root: '/www', }); }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file2.js'); }); it('transforms windowsExceptionEvent frames', () => { - const event = rewriteFrames.processEvent(windowsExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent lower-case prefix frames', () => { - const event = rewriteFrames.processEvent(windowsLowerCaseExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsLowerCaseExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent frames with no prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithoutPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithoutPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent frames with backslash prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithBackslashPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithBackslashPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); @@ -209,7 +199,7 @@ describe('RewriteFrames', () => { describe('can use custom iteratee', () => { beforeEach(() => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ iteratee: (frame: StackFrame) => ({ ...frame, function: 'whoops', @@ -218,7 +208,7 @@ describe('RewriteFrames', () => { }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![0].function).toEqual('whoops'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('/www/src/app/mo\\dule/file2.js'); @@ -228,8 +218,8 @@ describe('RewriteFrames', () => { describe('can process events that contain multiple stacktraces', () => { it('with defaults', () => { - rewriteFrames = getIntegration(); - const event = rewriteFrames.processEvent(multipleStacktracesEvent); + rewriteFrames = rewriteFramesIntegration(); + const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); @@ -242,10 +232,10 @@ describe('RewriteFrames', () => { }); it('with custom root', () => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ root: '/www', }); - const event = rewriteFrames.processEvent(multipleStacktracesEvent); + const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file2.js'); @@ -258,13 +248,13 @@ describe('RewriteFrames', () => { }); it('with custom iteratee', () => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ iteratee: (frame: StackFrame) => ({ ...frame, function: 'whoops', }), }); - const event = rewriteFrames.processEvent(multipleStacktracesEvent); + const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![0].function).toEqual('whoops'); @@ -285,17 +275,17 @@ describe('RewriteFrames', () => { describe('bails when unable to extract frames', () => { it('no exception values', () => { - rewriteFrames = getIntegration({}); + rewriteFrames = rewriteFramesIntegration({}); const brokenEvent = { exception: { values: undefined, }, }; - expect(rewriteFrames.processEvent(brokenEvent)).toEqual(brokenEvent); + expect(rewriteFrames.processEvent?.(brokenEvent, {}, {} as any)).toEqual(brokenEvent); }); it('no frames', () => { - rewriteFrames = getIntegration({}); + rewriteFrames = rewriteFramesIntegration({}); const brokenEvent = { exception: { values: [ @@ -305,7 +295,7 @@ describe('RewriteFrames', () => { ], }, }; - expect(rewriteFrames.processEvent(brokenEvent)).toEqual(brokenEvent); + expect(rewriteFrames.processEvent?.(brokenEvent, {}, {} as any)).toEqual(brokenEvent); }); }); }); diff --git a/packages/integrations/test/sessiontiming.test.ts b/packages/integrations/test/sessiontiming.test.ts index 85855324a6dc..339531ff48fa 100644 --- a/packages/integrations/test/sessiontiming.test.ts +++ b/packages/integrations/test/sessiontiming.test.ts @@ -1,15 +1,19 @@ -import { SessionTiming } from '../src/sessiontiming'; +import type { Event } from '@sentry/types'; +import { sessionTimingIntegration } from '../src/sessiontiming'; -// eslint-disable-next-line deprecation/deprecation -const sessionTiming = new SessionTiming(); +const sessionTiming = sessionTimingIntegration(); describe('SessionTiming', () => { it('should work as expected', () => { - const event = sessionTiming.processEvent({ - extra: { - some: 'value', + const event = sessionTiming.processEvent?.( + { + extra: { + some: 'value', + }, }, - }); + {}, + {} as any, + ) as Event; expect(typeof event.extra?.['session:start']).toBe('number'); expect(typeof event.extra?.['session:duration']).toBe('number'); diff --git a/packages/integrations/test/transaction.test.ts b/packages/integrations/test/transaction.test.ts deleted file mode 100644 index 617af17c171a..000000000000 --- a/packages/integrations/test/transaction.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Transaction } from '../src/transaction'; - -// eslint-disable-next-line deprecation/deprecation -const transaction = new Transaction(); - -describe('Transaction', () => { - describe('extracts info from module/function of the first `in_app` frame', () => { - it('using module only', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - in_app: false, - module: 'Foo', - }, - { - filename: '/some/file2.js', - in_app: true, - module: 'Qux', - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual('Qux/?'); - }); - - it('using function only', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - function: 'Bar', - in_app: false, - }, - { - filename: '/some/file2.js', - function: 'Baz', - in_app: true, - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual('?/Baz'); - }); - - it('using module and function', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - function: 'Bar', - in_app: true, - module: 'Foo', - }, - { - filename: '/some/file2.js', - function: 'Baz', - in_app: false, - module: 'Qux', - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual('Foo/Bar'); - }); - - it('using default', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - in_app: false, - }, - { - filename: '/some/file2.js', - in_app: true, - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual(''); - }); - - it('no value with no `in_app` frame', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - in_app: false, - }, - { - filename: '/some/file2.js', - in_app: false, - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toBeUndefined(); - }); - }); -}); diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts new file mode 100644 index 000000000000..effb99fc4e81 --- /dev/null +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -0,0 +1,49 @@ +import { defineIntegration } from '@sentry/core'; +import { rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/integrations'; +import type { IntegrationFn, StackFrame } from '@sentry/types'; +import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; + +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { + __rewriteFramesDistDir__?: string; +}; + +type StackFrameIteratee = (frame: StackFrame) => StackFrame; +interface RewriteFramesOptions { + root?: string; + prefix?: string; + iteratee?: StackFrameIteratee; +} + +export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { + // This value is injected at build time, based on the output directory specified in the build config. Though a default + // is set there, we set it here as well, just in case something has gone wrong with the injection. + const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + + if (distDirName) { + const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one + + // Normally we would use `path.resolve` to obtain the absolute path we will strip from the stack frame to align with + // the uploaded artifacts, however we don't have access to that API in edge so we need to be a bit more lax. + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- user input is escaped + const SOURCEMAP_FILENAME_REGEX = new RegExp(`.*${escapeStringForRegex(distDirAbsPath)}`); + + return originalRewriteFramesIntegration({ + iteratee: frame => { + frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); + return frame; + }, + ...options, + }); + } + + // Do nothing if we can't find a distDirName + return { + // eslint-disable-next-line deprecation/deprecation + name: 'RewriteFrames', + // eslint-disable-next-line @typescript-eslint/no-empty-function + setupOnce: () => {}, + processEvent: event => event, + }; +}) satisfies IntegrationFn; + +export const rewriteFramesIntegration = defineIntegration(customRewriteFramesIntegration); diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts new file mode 100644 index 000000000000..fe18c6a18be5 --- /dev/null +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -0,0 +1,49 @@ +import * as path from 'path'; +import { defineIntegration } from '@sentry/core'; +import { rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/integrations'; +import type { IntegrationFn, StackFrame } from '@sentry/types'; +import { escapeStringForRegex } from '@sentry/utils'; + +const globalWithInjectedValues = global as typeof global & { + __rewriteFramesDistDir__?: string; +}; + +type StackFrameIteratee = (frame: StackFrame) => StackFrame; +interface RewriteFramesOptions { + root?: string; + prefix?: string; + iteratee?: StackFrameIteratee; +} + +export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { + // This value is injected at build time, based on the output directory specified in the build config. Though a default + // is set there, we set it here as well, just in case something has gone wrong with the injection. + const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + + if (distDirName) { + // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so + // we can read in the project directory from the currently running process + const distDirAbsPath = path.resolve(distDirName).replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- user input is escaped + const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath)); + + return originalRewriteFramesIntegration({ + iteratee: frame => { + frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); + return frame; + }, + ...options, + }); + } + + // Do nothing if we can't find a distDirName + return { + // eslint-disable-next-line deprecation/deprecation + name: 'RewriteFrames', + // eslint-disable-next-line @typescript-eslint/no-empty-function + setupOnce: () => {}, + processEvent: event => event, + }; +}) satisfies IntegrationFn; + +export const rewriteFramesIntegration = defineIntegration(customRewriteFramesIntegration); diff --git a/packages/sveltekit/test/server/rewriteFramesIntegration.ts b/packages/sveltekit/test/server/rewriteFramesIntegration.ts index b2acdcf9ccaf..cb55fd62d1c7 100644 --- a/packages/sveltekit/test/server/rewriteFramesIntegration.ts +++ b/packages/sveltekit/test/server/rewriteFramesIntegration.ts @@ -1,4 +1,4 @@ -import { RewriteFrames } from '@sentry/integrations'; +import { rewriteFramesIntegration } from '@sentry/integrations'; import type { Event, StackFrame } from '@sentry/types'; import { basename } from '@sentry/utils'; @@ -25,10 +25,8 @@ describe('rewriteFramesIteratee', () => { module: '3-ab34d22f.js', }; - // eslint-disable-next-line deprecation/deprecation - const originalRewriteFrames = new RewriteFrames(); - // eslint-disable-next-line deprecation/deprecation - const rewriteFrames = new RewriteFrames({ iteratee: rewriteFramesIteratee }); + const originalRewriteFrames = rewriteFramesIntegration(); + const rewriteFrames = rewriteFramesIntegration({ iteratee: rewriteFramesIteratee }); const event: Event = { exception: { @@ -42,8 +40,8 @@ describe('rewriteFramesIteratee', () => { }, }; - const originalResult = originalRewriteFrames.processEvent(event); - const result = rewriteFrames.processEvent(event); + const originalResult = originalRewriteFrames.processEvent?.(event, {}, {} as any); + const result = rewriteFrames.processEvent?.(event, {}, {} as any) as Event; expect(result.exception?.values?.[0]?.stacktrace?.frames?.[0]).toEqual({ filename: 'app:///3-ab34d22f.js',