diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index f63fe20fdead..f013eb67f36a 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -90,3 +90,4 @@ export { wrap, } from './sdk'; export { GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations'; +export * from './integrations/index-functions'; diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 97abefea8242..3ac220a8891b 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -58,4 +58,4 @@ export type { SpanStatusType } from '@sentry/core'; export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; export { onProfilingStartRouteTransaction } from './profiling/hubextensions'; -export { BrowserProfilingIntegration } from './profiling/integration'; +export { BrowserProfilingIntegration, browserProfilingIntegration } from './profiling/integration'; diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 9799e6e73b4f..cf61c197474c 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -8,6 +8,8 @@ import type { HandlerDataFetch, HandlerDataHistory, HandlerDataXhr, + Integration, + IntegrationClass, IntegrationFn, } from '@sentry/types'; import type { @@ -55,7 +57,7 @@ const MAX_ALLOWED_STRING_LENGTH = 1024; const INTEGRATION_NAME = 'Breadcrumbs'; -const breadcrumbsIntegration: IntegrationFn = (options: Partial = {}) => { +export const breadcrumbsIntegration = ((options: Partial = {}) => { const _options = { console: true, dom: true, @@ -91,13 +93,32 @@ const breadcrumbsIntegration: IntegrationFn = (options: Partial & { + new ( + options?: Partial<{ + console: boolean; + dom: + | boolean + | { + serializeAttribute?: string | string[]; + maxStringLength?: number; + }; + fetch: boolean; + history: boolean; + sentry: boolean; + xhr: boolean; + }>, + ): Integration; +}; /** * Adds a breadcrumb for Sentry events or transactions if this option is enabled. diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts index a74a4b9ed9c6..13b7dc90c289 100644 --- a/packages/browser/src/integrations/dedupe.ts +++ b/packages/browser/src/integrations/dedupe.ts @@ -6,7 +6,7 @@ import { DEBUG_BUILD } from '../debug-build'; const INTEGRATION_NAME = 'Dedupe'; -const dedupeIntegration: IntegrationFn = () => { +export const dedupeIntegration = (() => { let previousEvent: Event | undefined; return { @@ -31,7 +31,7 @@ const dedupeIntegration: IntegrationFn = () => { return (previousEvent = currentEvent); }, }; -}; +}) satisfies IntegrationFn; /** Deduplication filter */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index e829b6f92845..5f0d8fe46d6a 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -1,6 +1,14 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { captureEvent, convertIntegrationFnToClass, getClient } from '@sentry/core'; -import type { Client, Event, IntegrationFn, Primitive, StackParser } from '@sentry/types'; +import type { + Client, + Event, + Integration, + IntegrationClass, + IntegrationFn, + Primitive, + StackParser, +} from '@sentry/types'; import { addGlobalErrorInstrumentationHandler, addGlobalUnhandledRejectionInstrumentationHandler, @@ -22,7 +30,7 @@ type GlobalHandlersIntegrations = Record = {}) => { +export const globalHandlersIntegration = ((options: Partial = {}) => { const _options = { onerror: true, onunhandledrejection: true, @@ -45,11 +53,14 @@ const globalHandlersIntegrations: IntegrationFn = (options: Partial & { new (options?: Partial): Integration }; function _installGlobalOnErrorHandler(client: Client): void { addGlobalErrorInstrumentationHandler(data => { diff --git a/packages/browser/src/integrations/httpcontext.ts b/packages/browser/src/integrations/httpcontext.ts index d46f29257306..efd50dc149cb 100644 --- a/packages/browser/src/integrations/httpcontext.ts +++ b/packages/browser/src/integrations/httpcontext.ts @@ -5,7 +5,7 @@ import { WINDOW } from '../helpers'; const INTEGRATION_NAME = 'HttpContext'; -const httpContextIntegration: IntegrationFn = () => { +export const httpContextIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -31,7 +31,7 @@ const httpContextIntegration: IntegrationFn = () => { event.request = request; }, }; -}; +}) satisfies IntegrationFn; /** HttpContext integration collects information about HTTP request headers */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/browser/src/integrations/index-functions.ts b/packages/browser/src/integrations/index-functions.ts new file mode 100644 index 000000000000..38962a16b720 --- /dev/null +++ b/packages/browser/src/integrations/index-functions.ts @@ -0,0 +1,6 @@ +export { globalHandlersIntegration } from './globalhandlers'; +export { browserApiErrorsIntegration } from './trycatch'; +export { breadcrumbsIntegration } from './breadcrumbs'; +export { linkedErrorsIntegration } from './linkederrors'; +export { httpContextIntegration } from './httpcontext'; +export { dedupeIntegration } from './dedupe'; diff --git a/packages/browser/src/integrations/linkederrors.ts b/packages/browser/src/integrations/linkederrors.ts index bb351bcdc3c6..a62de5c66381 100644 --- a/packages/browser/src/integrations/linkederrors.ts +++ b/packages/browser/src/integrations/linkederrors.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { applyAggregateErrorsToEvent } from '@sentry/utils'; import { exceptionFromError } from '../eventbuilder'; @@ -13,7 +13,7 @@ const DEFAULT_LIMIT = 5; const INTEGRATION_NAME = 'LinkedErrors'; -const linkedErrorsIntegration: IntegrationFn = (options: LinkedErrorsOptions = {}) => { +export const linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { const limit = options.limit || DEFAULT_LIMIT; const key = options.key || DEFAULT_KEY; @@ -36,8 +36,11 @@ const linkedErrorsIntegration: IntegrationFn = (options: LinkedErrorsOptions = { ); }, }; -}; +}) satisfies IntegrationFn; /** Aggregrate linked errors in an event. */ // eslint-disable-next-line deprecation/deprecation -export const LinkedErrors = convertIntegrationFnToClass(INTEGRATION_NAME, linkedErrorsIntegration); +export const LinkedErrors = convertIntegrationFnToClass( + INTEGRATION_NAME, + linkedErrorsIntegration, +) as IntegrationClass & { new (options?: { key?: string; limit?: number }): Integration }; diff --git a/packages/browser/src/integrations/trycatch.ts b/packages/browser/src/integrations/trycatch.ts index f555acfcdc9c..732b243bf64d 100644 --- a/packages/browser/src/integrations/trycatch.ts +++ b/packages/browser/src/integrations/trycatch.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { IntegrationFn, WrappedFunction } from '@sentry/types'; +import type { Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types'; import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils'; import { WINDOW, wrap } from '../helpers'; @@ -50,7 +50,7 @@ interface TryCatchOptions { eventTarget: boolean | string[]; } -const tryCatchIntegration: IntegrationFn = (options: Partial = {}) => { +export const browserApiErrorsIntegration = ((options: Partial = {}) => { const _options = { XMLHttpRequest: true, eventTarget: true, @@ -88,11 +88,22 @@ const tryCatchIntegration: IntegrationFn = (options: Partial = } }, }; -}; +}) satisfies IntegrationFn; /** Wrap timer functions and event targets to catch errors and provide better meta data */ // eslint-disable-next-line deprecation/deprecation -export const TryCatch = convertIntegrationFnToClass(INTEGRATION_NAME, tryCatchIntegration); +export const TryCatch = convertIntegrationFnToClass( + INTEGRATION_NAME, + browserApiErrorsIntegration, +) as IntegrationClass & { + new (options?: { + setTimeout: boolean; + setInterval: boolean; + requestAnimationFrame: boolean; + XMLHttpRequest: boolean; + eventTarget: boolean | string[]; + }): Integration; +}; function _wrapTimeFunction(original: () => void): () => number { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index c6e478e8554d..9c9f4365ddea 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -18,7 +18,7 @@ import { const INTEGRATION_NAME = 'BrowserProfiling'; -const browserProfilingIntegration: IntegrationFn = () => { +export const browserProfilingIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -100,7 +100,7 @@ const browserProfilingIntegration: IntegrationFn = () => { }); }, }; -}; +}) satisfies IntegrationFn; /** * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"] diff --git a/packages/browser/test/unit/profiling/integration.test.ts b/packages/browser/test/unit/profiling/integration.test.ts index 0c7eb35f60e2..b69d3a52d655 100644 --- a/packages/browser/test/unit/profiling/integration.test.ts +++ b/packages/browser/test/unit/profiling/integration.test.ts @@ -36,7 +36,6 @@ describe('BrowserProfilingIntegration', () => { Sentry.init({ tracesSampleRate: 1, profilesSampleRate: 1, - debug: true, environment: 'test-environment', dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', transport: _opts => { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index bb033496da02..1a0146520326 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -94,3 +94,5 @@ const INTEGRATIONS = { }; export { INTEGRATIONS as Integrations }; + +export { bunServerIntegration } from './integrations/bunserver'; diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index fec3aae439af..6654dc0526ee 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -13,14 +13,14 @@ import { getSanitizedUrlString, parseUrl } from '@sentry/utils'; const INTEGRATION_NAME = 'BunServer'; -const bunServerIntegration: IntegrationFn = () => { +export const bunServerIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { instrumentBunServe(); }, }; -}; +}) satisfies IntegrationFn; /** * Instruments `Bun.serve` to automatically create transactions and capture errors. diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e277c01f2dbe..49b150addefd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -70,6 +70,11 @@ export { convertIntegrationFnToClass, } from './integration'; export { FunctionToString, InboundFilters, LinkedErrors } from './integrations'; +export { inboundFiltersIntegration } from './integrations/inboundfilters'; +export { linkedErrorsIntegration } from './integrations/linkederrors'; +export { functionToStringIntegration } from './integrations/functiontostring'; +export { requestDataIntegration } from './integrations/requestdata'; +export { moduleMetadataIntegration } from './integrations/metadata'; export { applyScopeDataToEvent, mergeScopeData } from './utils/applyScopeDataToEvent'; export { prepareEvent } from './utils/prepareEvent'; export { createCheckInEnvelope } from './checkin'; diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 4b4a3177c428..08cfe5be22b8 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -1,4 +1,4 @@ -import type { Client, Event, EventHint, Integration, IntegrationFn, Options } from '@sentry/types'; +import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn, Options } from '@sentry/types'; import { arrayify, logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; @@ -166,21 +166,11 @@ function findIndex(arr: T[], callback: (item: T) => boolean): number { * * @deprecated This will be removed in v8! */ -export function convertIntegrationFnToClass( - name: string, - fn: Fn, -): Integration & { - id: string; - new (...args: Parameters): Integration & ReturnType; -} { +export function convertIntegrationFnToClass(name: string, fn: IntegrationFn): IntegrationClass { return Object.assign( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function ConvertedIntegration(...rest: Parameters) { - return fn(...rest); + function ConvertedIntegration(options?: Options) { + return fn(options); }, { id: name }, - ) as unknown as Integration & { - id: string; - new (...args: Parameters): Integration & ReturnType; - }; + ) as unknown as IntegrationClass; } diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index 124085c9c6c6..2323235d6840 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -6,7 +6,7 @@ let originalFunctionToString: () => void; const INTEGRATION_NAME = 'FunctionToString'; -const functionToStringIntegration: IntegrationFn = () => { +export const functionToStringIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { @@ -26,7 +26,7 @@ const functionToStringIntegration: IntegrationFn = () => { } }, }; -}; +}) satisfies IntegrationFn; /** Patch toString calls to return proper name for wrapped functions */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 6d2ec6dfc799..dced798832e5 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -1,4 +1,4 @@ -import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; import { getEventDescription, logger, stringMatchesSomePattern } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; @@ -30,7 +30,7 @@ export interface InboundFiltersOptions { } const INTEGRATION_NAME = 'InboundFilters'; -const inboundFiltersIntegration: IntegrationFn = (options: Partial) => { +export const inboundFiltersIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -41,11 +41,26 @@ const inboundFiltersIntegration: IntegrationFn = (options: Partial & { + new ( + options?: Partial<{ + allowUrls: Array; + denyUrls: Array; + ignoreErrors: Array; + ignoreTransactions: Array; + ignoreInternal: boolean; + disableErrorDefaults: boolean; + disableTransactionDefaults: boolean; + }>, + ): Integration; +}; function _mergeOptions( internalOptions: Partial = {}, diff --git a/packages/core/src/integrations/linkederrors.ts b/packages/core/src/integrations/linkederrors.ts index 25e6417023f2..b976940bf27a 100644 --- a/packages/core/src/integrations/linkederrors.ts +++ b/packages/core/src/integrations/linkederrors.ts @@ -1,4 +1,4 @@ -import type { IntegrationFn } from '@sentry/types'; +import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { applyAggregateErrorsToEvent, exceptionFromError } from '@sentry/utils'; import { convertIntegrationFnToClass } from '../integration'; @@ -12,7 +12,7 @@ const DEFAULT_LIMIT = 5; const INTEGRATION_NAME = 'LinkedErrors'; -const linkedErrorsIntegration: IntegrationFn = (options: LinkedErrorsOptions = {}) => { +export const linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { const limit = options.limit || DEFAULT_LIMIT; const key = options.key || DEFAULT_KEY; @@ -34,8 +34,11 @@ const linkedErrorsIntegration: IntegrationFn = (options: LinkedErrorsOptions = { ); }, }; -}; +}) satisfies IntegrationFn; /** Adds SDK info to an event. */ // eslint-disable-next-line deprecation/deprecation -export const LinkedErrors = convertIntegrationFnToClass(INTEGRATION_NAME, linkedErrorsIntegration); +export const LinkedErrors = convertIntegrationFnToClass( + INTEGRATION_NAME, + linkedErrorsIntegration, +) as IntegrationClass & { new (options?: { key?: string; limit?: number }): Integration }; diff --git a/packages/core/src/integrations/metadata.ts b/packages/core/src/integrations/metadata.ts index a18fc5dffebc..5300efc70775 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -6,7 +6,7 @@ import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metad const INTEGRATION_NAME = 'ModuleMetadata'; -const moduleMetadataIntegration: IntegrationFn = () => { +export const moduleMetadataIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -37,7 +37,7 @@ const moduleMetadataIntegration: IntegrationFn = () => { return event; }, }; -}; +}) satisfies IntegrationFn; /** * Adds module metadata to stack frames. diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts index 76d29b4751b7..6967c152580a 100644 --- a/packages/core/src/integrations/requestdata.ts +++ b/packages/core/src/integrations/requestdata.ts @@ -1,4 +1,4 @@ -import type { Client, IntegrationFn, Transaction } from '@sentry/types'; +import type { Client, Integration, IntegrationClass, IntegrationFn, Transaction } from '@sentry/types'; import type { AddRequestDataToEventOptions, TransactionNamingScheme } from '@sentry/utils'; import { addRequestDataToEvent, extractPathForTransaction } from '@sentry/utils'; import { convertIntegrationFnToClass } from '../integration'; @@ -47,7 +47,7 @@ const DEFAULT_OPTIONS = { const INTEGRATION_NAME = 'RequestData'; -const requestDataIntegration: IntegrationFn = (options: RequestDataIntegrationOptions = {}) => { +export const requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) => { const _addRequestData = addRequestDataToEvent; const _options: Required = { ...DEFAULT_OPTIONS, @@ -129,12 +129,39 @@ const requestDataIntegration: IntegrationFn = (options: RequestDataIntegrationOp return processedEvent; }, }; -}; +}) satisfies IntegrationFn; /** Add data about a request to an event. Primarily for use in Node-based SDKs, but included in `@sentry/integrations` * so it can be used in cross-platform SDKs like `@sentry/nextjs`. */ // eslint-disable-next-line deprecation/deprecation -export const RequestData = convertIntegrationFnToClass(INTEGRATION_NAME, requestDataIntegration); +export const RequestData = convertIntegrationFnToClass( + INTEGRATION_NAME, + requestDataIntegration, +) as IntegrationClass & { + new (options?: { + /** + * Controls what data is pulled from the request and added to the event + */ + include?: { + cookies?: boolean; + data?: boolean; + headers?: boolean; + ip?: boolean; + query_string?: boolean; + url?: boolean; + user?: + | boolean + | { + id?: boolean; + username?: boolean; + email?: boolean; + }; + }; + + /** Whether to identify transactions by parameterized path, parameterized path with method, or handler name */ + transactionNamingScheme?: TransactionNamingScheme; + }): Integration; +}; /** Convert this integration's options to match what `addRequestDataToEvent` expects */ /** TODO: Can possibly be deleted once https://github.com/getsentry/sentry-javascript/issues/5718 is fixed */ diff --git a/packages/core/src/metrics/exports.ts b/packages/core/src/metrics/exports.ts index 03e81ed49f0e..3031b2a112d6 100644 --- a/packages/core/src/metrics/exports.ts +++ b/packages/core/src/metrics/exports.ts @@ -5,7 +5,7 @@ import { DEBUG_BUILD } from '../debug-build'; import { getClient, getCurrentScope } from '../exports'; import { spanToJSON } from '../utils/spanUtils'; import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_METRIC_TYPE } from './constants'; -import { MetricsAggregator } from './integration'; +import { MetricsAggregator, metricsAggregatorIntegration } from './integration'; import type { MetricType } from './types'; interface MetricData { @@ -90,4 +90,5 @@ export const metrics = { set, gauge, MetricsAggregator, + metricsAggregatorIntegration, }; diff --git a/packages/core/src/metrics/integration.ts b/packages/core/src/metrics/integration.ts index 5cadf47ce3f2..b13bf1f47a64 100644 --- a/packages/core/src/metrics/integration.ts +++ b/packages/core/src/metrics/integration.ts @@ -5,7 +5,7 @@ import { BrowserMetricsAggregator } from './browser-aggregator'; const INTEGRATION_NAME = 'MetricsAggregator'; -const metricsAggregatorIntegration: IntegrationFn = () => { +export const metricsAggregatorIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -14,7 +14,7 @@ const metricsAggregatorIntegration: IntegrationFn = () => { client.metricsAggregator = new BrowserMetricsAggregator(client); }, }; -}; +}) satisfies IntegrationFn; /** * Enables Sentry metrics monitoring. diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index 829ef4de991b..779567a4f1cb 100644 --- a/packages/core/test/lib/integration.test.ts +++ b/packages/core/test/lib/integration.test.ts @@ -675,7 +675,7 @@ describe('convertIntegrationFnToClass', () => { expect(IntegrationClass.id).toBe('testName'); - // @ts-expect-error This should fail TS without options + // not type safe options by default :( new IntegrationClass(); const integration = new IntegrationClass({ num: 3 }); diff --git a/packages/core/test/lib/integrations/functiontostring.test.ts b/packages/core/test/lib/integrations/functiontostring.test.ts index cbe664ae10fc..8647662b14b8 100644 --- a/packages/core/test/lib/integrations/functiontostring.test.ts +++ b/packages/core/test/lib/integrations/functiontostring.test.ts @@ -1,3 +1,4 @@ +import type { Hub } from '@sentry/types'; import { fill } from '../../../../utils/src/object'; import { FunctionToString } from '../../../src/integrations/functiontostring'; @@ -18,7 +19,10 @@ describe('FunctionToString', () => { expect(foo.bar.toString()).not.toBe(originalFunction); const fts = new FunctionToString(); - fts.setupOnce(); + fts.setupOnce( + () => {}, + () => ({}) as Hub, + ); expect(foo.bar.toString()).toBe(originalFunction); }); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 2658d6f31e36..89619a7c0ec5 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -90,3 +90,9 @@ const INTEGRATIONS = { }; export { INTEGRATIONS as Integrations }; + +export { denoContextIntegration } from './integrations/context'; +export { denoContextLinesIntegration } from './integrations/contextlines'; +export { denoCronIntegration } from './integrations/deno-cron'; +export { globalHandlersIntegration } from './integrations/globalhandlers'; +export { normalizePathsIntegration } from './integrations/normalizepaths'; diff --git a/packages/deno/src/integrations/context.ts b/packages/deno/src/integrations/context.ts index 426d296f6efc..d106d0117443 100644 --- a/packages/deno/src/integrations/context.ts +++ b/packages/deno/src/integrations/context.ts @@ -52,7 +52,7 @@ async function addDenoRuntimeContext(event: Event): Promise { return event; } -const denoContextIntegration: IntegrationFn = () => { +export const denoContextIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -61,7 +61,7 @@ const denoContextIntegration: IntegrationFn = () => { return addDenoRuntimeContext(event); }, }; -}; +}) satisfies IntegrationFn; /** Adds Deno context to events. */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/deno/src/integrations/contextlines.ts b/packages/deno/src/integrations/contextlines.ts index f3099151908a..1adcc05ee929 100644 --- a/packages/deno/src/integrations/contextlines.ts +++ b/packages/deno/src/integrations/contextlines.ts @@ -47,7 +47,7 @@ interface ContextLinesOptions { frameContextLines?: number; } -const denoContextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = {}) => { +export const denoContextLinesIntegration = ((options: ContextLinesOptions = {}) => { const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT; return { @@ -58,7 +58,7 @@ const denoContextLinesIntegration: IntegrationFn = (options: ContextLinesOptions return addSourceContext(event, contextLines); }, }; -}; +}) satisfies IntegrationFn; /** Add node modules / packages to the event */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/deno/src/integrations/deno-cron.ts b/packages/deno/src/integrations/deno-cron.ts index 73c1bc1954fb..c7efb366fe8d 100644 --- a/packages/deno/src/integrations/deno-cron.ts +++ b/packages/deno/src/integrations/deno-cron.ts @@ -11,7 +11,7 @@ const INTEGRATION_NAME = 'DenoCron'; const SETUP_CLIENTS = new WeakMap(); -const denoCronIntegration = (() => { +export const denoCronIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { @@ -36,6 +36,7 @@ const denoCronIntegration = (() => { options = opt1; } + /** When a cron was called. */ async function cronCalled(): Promise { if (SETUP_CLIENTS.has(getClient() as Client)) { return; diff --git a/packages/deno/src/integrations/globalhandlers.ts b/packages/deno/src/integrations/globalhandlers.ts index 05b46b58c7a9..ccb3f75277c6 100644 --- a/packages/deno/src/integrations/globalhandlers.ts +++ b/packages/deno/src/integrations/globalhandlers.ts @@ -13,7 +13,7 @@ type GlobalHandlersIntegrations = Record { +export const globalHandlersIntegration = ((options?: GlobalHandlersIntegrations) => { const _options = { error: true, unhandledrejection: true, @@ -33,7 +33,7 @@ const globalHandlersIntegration: IntegrationFn = (options?: GlobalHandlersIntegr } }, }; -}; +}) satisfies IntegrationFn; /** Global handlers */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index 39a3bab5a1c5..3543a10b5639 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -55,10 +55,11 @@ function getCwd(): string | undefined { return undefined; } -const normalizePathsIntegration: IntegrationFn = () => { +export const normalizePathsIntegration = (() => { // Cached here let appRoot: string | undefined; + /** Get the app root, and cache it after it was first fetched. */ function getAppRoot(error: Error): string | undefined { if (appRoot === undefined) { appRoot = getCwd() || appRootFromErrorStack(error); @@ -95,7 +96,7 @@ const normalizePathsIntegration: IntegrationFn = () => { return event; }, }; -}; +}) satisfies IntegrationFn; /** Normalises paths to the app root directory. */ // eslint-disable-next-line deprecation/deprecation diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index 4d9aa04ce116..e58b1a589e31 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -1,5 +1,5 @@ import { captureException, captureMessage, convertIntegrationFnToClass, getClient, withScope } from '@sentry/core'; -import type { CaptureContext, IntegrationFn } from '@sentry/types'; +import type { CaptureContext, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { CONSOLE_LEVELS, GLOBAL_OBJ, @@ -15,7 +15,7 @@ interface CaptureConsoleOptions { const INTEGRATION_NAME = 'CaptureConsole'; -const captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => { +export const captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => { const levels = options.levels || CONSOLE_LEVELS; return { @@ -40,7 +40,10 @@ const captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => { /** Send Console API calls as Sentry Events */ // eslint-disable-next-line deprecation/deprecation -export const CaptureConsole = convertIntegrationFnToClass(INTEGRATION_NAME, captureConsoleIntegration); +export const CaptureConsole = convertIntegrationFnToClass( + INTEGRATION_NAME, + captureConsoleIntegration, +) as IntegrationClass & { new (options?: { levels?: string[] }): Integration }; 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 143fb0232bec..173b452865a4 100644 --- a/packages/integrations/src/contextlines.ts +++ b/packages/integrations/src/contextlines.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; import { GLOBAL_OBJ, addContextToFrame, stripUrlQueryAndFragment } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; @@ -18,7 +18,7 @@ interface ContextLinesOptions { frameContextLines?: number; } -const contextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = {}) => { +export const contextLinesIntegration = ((options: ContextLinesOptions = {}) => { const contextLines = options.frameContextLines != null ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT; return { @@ -29,7 +29,7 @@ const contextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = { return addSourceContext(event, contextLines); }, }; -}; +}) satisfies IntegrationFn; /** * Collects source context lines around the lines of stackframes pointing to JS embedded in @@ -43,7 +43,10 @@ const contextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = { * by our backend (e.g. due to a login-protected page). */ // eslint-disable-next-line deprecation/deprecation -export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration); +export const ContextLines = convertIntegrationFnToClass( + INTEGRATION_NAME, + contextLinesIntegration, +) as IntegrationClass & { new (options?: { frameContextLines?: number }): Integration }; /** * Processes an event and adds context lines. diff --git a/packages/integrations/src/debug.ts b/packages/integrations/src/debug.ts index 8e20ce7f9dfc..e1e7d3becf78 100644 --- a/packages/integrations/src/debug.ts +++ b/packages/integrations/src/debug.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, EventHint, IntegrationFn } from '@sentry/types'; +import type { Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; const INTEGRATION_NAME = 'Debug'; @@ -11,7 +11,7 @@ interface DebugOptions { debugger?: boolean; } -const debugIntegration = ((options: DebugOptions = {}) => { +export const debugIntegration = ((options: DebugOptions = {}) => { const _options = { debugger: false, stringify: false, @@ -58,4 +58,12 @@ const debugIntegration = ((options: DebugOptions = {}) => { * This integration should not be used in production */ // eslint-disable-next-line deprecation/deprecation -export const Debug = convertIntegrationFnToClass(INTEGRATION_NAME, debugIntegration); +export const Debug = convertIntegrationFnToClass( + INTEGRATION_NAME, + debugIntegration, +) as IntegrationClass & { + new (options?: { + stringify?: boolean; + debugger?: boolean; + }): Integration; +}; diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index 97a47193b19a..7e0d5029a270 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -6,7 +6,7 @@ import { DEBUG_BUILD } from './debug-build'; const INTEGRATION_NAME = 'Dedupe'; -const dedupeIntegration = (() => { +export const dedupeIntegration = (() => { let previousEvent: Event | undefined; return { diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 6b340a1283bc..bc7ad0a863a8 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -1,5 +1,13 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Contexts, Event, EventHint, ExtendedError, IntegrationFn } from '@sentry/types'; +import type { + Contexts, + Event, + EventHint, + ExtendedError, + Integration, + IntegrationClass, + IntegrationFn, +} from '@sentry/types'; import { addNonEnumerableProperty, isError, isPlainObject, logger, normalize } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; @@ -20,7 +28,7 @@ interface ExtraErrorDataOptions { captureErrorCause: boolean; } -const extraErrorDataIntegration = ((options: Partial = {}) => { +export const extraErrorDataIntegration = ((options: Partial = {}) => { const depth = options.depth || 3; // TODO(v8): Flip the default for this option to true @@ -38,7 +46,17 @@ const extraErrorDataIntegration = ((options: Partial = {} /** Extract additional data for from original exceptions. */ // eslint-disable-next-line deprecation/deprecation -export const ExtraErrorData = convertIntegrationFnToClass(INTEGRATION_NAME, extraErrorDataIntegration); +export const ExtraErrorData = convertIntegrationFnToClass( + INTEGRATION_NAME, + extraErrorDataIntegration, +) as IntegrationClass & { + new ( + options?: Partial<{ + depth: number; + captureErrorCause: boolean; + }>, + ): Integration; +}; function _enhanceEventWithErrorData( event: Event, diff --git a/packages/integrations/src/httpclient.ts b/packages/integrations/src/httpclient.ts index 0ef2dcca3f27..5e38a1ae2c40 100644 --- a/packages/integrations/src/httpclient.ts +++ b/packages/integrations/src/httpclient.ts @@ -1,5 +1,12 @@ import { captureEvent, convertIntegrationFnToClass, getClient, isSentryRequestUrl } from '@sentry/core'; -import type { Client, Event as SentryEvent, IntegrationFn, SentryWrappedXMLHttpRequest } from '@sentry/types'; +import type { + Client, + Event as SentryEvent, + Integration, + IntegrationClass, + IntegrationFn, + SentryWrappedXMLHttpRequest, +} from '@sentry/types'; import { GLOBAL_OBJ, SENTRY_XHR_DATA_KEY, @@ -38,7 +45,7 @@ interface HttpClientOptions { failedRequestTargets: HttpRequestTarget[]; } -const httpClientIntegration = ((options: Partial = {}) => { +export const httpClientIntegration = ((options: Partial = {}) => { const _options: HttpClientOptions = { failedRequestStatusCodes: [[500, 599]], failedRequestTargets: [/.*/], @@ -58,7 +65,15 @@ const httpClientIntegration = ((options: Partial = {}) => { /** HTTPClient integration creates events for failed client side HTTP requests. */ // eslint-disable-next-line deprecation/deprecation -export const HttpClient = convertIntegrationFnToClass(INTEGRATION_NAME, httpClientIntegration); +export const HttpClient = convertIntegrationFnToClass( + INTEGRATION_NAME, + httpClientIntegration, +) as IntegrationClass & { + new (options?: { + failedRequestStatusCodes: HttpStatusCodeRange[]; + failedRequestTargets: HttpRequestTarget[]; + }): Integration; +}; /** * Interceptor function for fetch requests diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index b7dcc1f4716a..2b7b986345ec 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -7,6 +7,7 @@ export { Offline } from './offline'; export { ReportingObserver } from './reportingobserver'; export { RewriteFrames } from './rewriteframes'; export { SessionTiming } from './sessiontiming'; +// eslint-disable-next-line deprecation/deprecation export { Transaction } from './transaction'; export { HttpClient } from './httpclient'; export { ContextLines } from './contextlines'; diff --git a/packages/integrations/src/reportingobserver.ts b/packages/integrations/src/reportingobserver.ts index cd8c28b54f8c..c7111111d729 100644 --- a/packages/integrations/src/reportingobserver.ts +++ b/packages/integrations/src/reportingobserver.ts @@ -1,5 +1,5 @@ import { captureMessage, convertIntegrationFnToClass, getClient, withScope } from '@sentry/core'; -import type { Client, IntegrationFn } from '@sentry/types'; +import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { GLOBAL_OBJ, supportsReportingObserver } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; @@ -48,9 +48,10 @@ interface ReportingObserverOptions { const SETUP_CLIENTS = new WeakMap(); -const reportingObserverIntegration = ((options: ReportingObserverOptions = {}) => { +export const reportingObserverIntegration = ((options: ReportingObserverOptions = {}) => { const types = options.types || ['crash', 'deprecation', 'intervention']; + /** Handler for the reporting observer. */ function handler(reports: Report[]): void { if (!SETUP_CLIENTS.has(getClient() as Client)) { return; @@ -116,4 +117,11 @@ const reportingObserverIntegration = ((options: ReportingObserverOptions = {}) = /** Reporting API integration - https://w3c.github.io/reporting/ */ // eslint-disable-next-line deprecation/deprecation -export const ReportingObserver = convertIntegrationFnToClass(INTEGRATION_NAME, reportingObserverIntegration); +export const ReportingObserver = convertIntegrationFnToClass( + INTEGRATION_NAME, + reportingObserverIntegration, +) as IntegrationClass & { + new (options?: { + types?: ReportTypes[]; + }): Integration; +}; diff --git a/packages/integrations/src/rewriteframes.ts b/packages/integrations/src/rewriteframes.ts index 7bb5957607b2..0f1e12e9a95f 100644 --- a/packages/integrations/src/rewriteframes.ts +++ b/packages/integrations/src/rewriteframes.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, IntegrationFn, StackFrame, Stacktrace } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame, Stacktrace } from '@sentry/types'; import { basename, relative } from '@sentry/utils'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -12,7 +12,7 @@ interface RewriteFramesOptions { iteratee?: StackFrameIteratee; } -const rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { +export const rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { const root = options.root; const prefix = options.prefix || 'app:///'; @@ -41,6 +41,7 @@ const rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { return frame; }); + /** Process an exception event. */ function _processExceptionsEvent(event: Event): Event { try { return { @@ -60,6 +61,7 @@ const rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { } } + /** Process a stack trace. */ function _processStacktrace(stacktrace?: Stacktrace): Stacktrace { return { ...stacktrace, @@ -85,4 +87,9 @@ const rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { /** Rewrite event frames paths */ // eslint-disable-next-line deprecation/deprecation -export const RewriteFrames = convertIntegrationFnToClass(INTEGRATION_NAME, rewriteFramesIntegration); +export const RewriteFrames = convertIntegrationFnToClass( + INTEGRATION_NAME, + rewriteFramesIntegration, +) as IntegrationClass & { + new (options?: { root?: string; prefix?: string; iteratee?: StackFrameIteratee }): Integration; +}; diff --git a/packages/integrations/src/sessiontiming.ts b/packages/integrations/src/sessiontiming.ts index b3511e6c3527..e031a9dc17d6 100644 --- a/packages/integrations/src/sessiontiming.ts +++ b/packages/integrations/src/sessiontiming.ts @@ -3,7 +3,7 @@ import type { IntegrationFn } from '@sentry/types'; const INTEGRATION_NAME = 'SessionTiming'; -const sessionTimingIntegration = (() => { +export const sessionTimingIntegration = (() => { const startTime = Date.now(); return { diff --git a/packages/integrations/src/transaction.ts b/packages/integrations/src/transaction.ts index a7e5aa5a1745..f378efa6f322 100644 --- a/packages/integrations/src/transaction.ts +++ b/packages/integrations/src/transaction.ts @@ -3,7 +3,8 @@ import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; const INTEGRATION_NAME = 'Transaction'; -const transactionIntegration = (() => { +// TODO: This will be removed in v8. +const transactionFromExceptionStacktraceIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -26,9 +27,12 @@ const transactionIntegration = (() => { }; }) satisfies IntegrationFn; -/** Add node transaction to the event */ +/** + * 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); +export const Transaction = convertIntegrationFnToClass(INTEGRATION_NAME, transactionFromExceptionStacktraceIntegration); function _getFramesFromEvent(event: Event): StackFrame[] { const exception = event.exception && event.exception.values && event.exception.values[0]; diff --git a/packages/integrations/test/captureconsole.test.ts b/packages/integrations/test/captureconsole.test.ts index 23a410f0bb33..ef5e237b6f26 100644 --- a/packages/integrations/test/captureconsole.test.ts +++ b/packages/integrations/test/captureconsole.test.ts @@ -66,7 +66,7 @@ describe('CaptureConsole setup', () => { describe('monkeypatching', () => { it('should patch user-configured console levels', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'warn'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.error('msg 1'); GLOBAL_OBJ.console.log('msg 2'); @@ -77,7 +77,7 @@ describe('CaptureConsole setup', () => { it('should fall back to default console levels if none are provided', () => { const captureConsoleIntegration = new CaptureConsole(); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); // Assert has a special handling (['debug', 'info', 'warn', 'error', 'log', 'trace'] as const).forEach(key => { @@ -91,7 +91,7 @@ describe('CaptureConsole setup', () => { it('should not wrap any functions with an empty levels option', () => { const captureConsoleIntegration = new CaptureConsole({ levels: [] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); CONSOLE_LEVELS.forEach(key => { GLOBAL_OBJ.console[key]('msg'); @@ -108,7 +108,7 @@ describe('CaptureConsole setup', () => { const captureConsoleIntegration = new CaptureConsole(); expect(() => { - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); }).not.toThrow(); // reinstate initial console @@ -117,7 +117,7 @@ describe('CaptureConsole setup', () => { it('should send empty arguments as extra data', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.log(); @@ -127,7 +127,7 @@ describe('CaptureConsole setup', () => { it('should add an event processor that sets the `logger` field of events', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); // call a wrapped function GLOBAL_OBJ.console.log('some message'); @@ -143,7 +143,7 @@ describe('CaptureConsole setup', () => { it('should capture message on a failed assertion', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 3); @@ -157,7 +157,7 @@ describe('CaptureConsole setup', () => { it('should capture correct message on a failed assertion with message', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 3, 'expression is false'); @@ -171,14 +171,14 @@ describe('CaptureConsole setup', () => { it('should not capture message on a successful assertion', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.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 = new CaptureConsole({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); @@ -192,7 +192,7 @@ describe('CaptureConsole setup', () => { it('should capture exception on `console.error` when no levels are provided in constructor', () => { const captureConsoleIntegration = new CaptureConsole(); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); @@ -206,7 +206,7 @@ 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 = new CaptureConsole({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error('Something went wrong', someError); @@ -220,7 +220,7 @@ describe('CaptureConsole setup', () => { it('should capture message on `console.log` when no levels are provided in constructor', () => { const captureConsoleIntegration = new CaptureConsole(); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.error('some message'); @@ -233,7 +233,7 @@ describe('CaptureConsole setup', () => { it('should capture message when console logs a non-error object with level set to "error"', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.error('some non-error message'); @@ -247,7 +247,7 @@ describe('CaptureConsole setup', () => { it('should capture a message for non-error log levels', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['info'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.info('some message'); @@ -265,7 +265,7 @@ describe('CaptureConsole setup', () => { GLOBAL_OBJ.console.log = mockConsoleLog; const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); GLOBAL_OBJ.console.log('some message 1', 'some message 2'); @@ -278,7 +278,7 @@ describe('CaptureConsole setup', () => { it('should not wrap any levels that are not members of console', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'someNonExistingLevel', 'error'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); // The provided level should not be created expect((GLOBAL_OBJ.console as any)['someNonExistingLevel']).toBeUndefined(); @@ -287,7 +287,7 @@ describe('CaptureConsole setup', () => { it('should wrap the console when the client does not have a registered captureconsole integration, but not capture any messages', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'error'] }); // when `setup` is not called on the current client, it will not trigger - captureConsoleIntegration.setup({} as Client); + captureConsoleIntegration.setup!({} as Client); // Should not capture messages GLOBAL_OBJ.console.log('some message'); @@ -298,7 +298,7 @@ describe('CaptureConsole setup', () => { originalConsoleMethods.log = undefined; const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.setup!(mockClient); expect(() => { GLOBAL_OBJ.console.log('some message'); @@ -309,7 +309,7 @@ describe('CaptureConsole setup', () => { // const addExceptionMechanismSpy = jest.spyOn(utils, 'addExceptionMechanism'); const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + captureConsoleIntegration.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 1cb952f26a5a..9d09f5e1a470 100644 --- a/packages/integrations/test/debug.test.ts +++ b/packages/integrations/test/debug.test.ts @@ -2,11 +2,7 @@ import type { Client, Event, EventHint, Integration } from '@sentry/types'; import { Debug } from '../src/debug'; -interface IntegrationWithSetup extends Integration { - setup: (client: Client) => void; -} - -function testEventLogged(integration: IntegrationWithSetup, testEvent?: Event, testEventHint?: EventHint) { +function testEventLogged(integration: Integration, testEvent?: Event, testEventHint?: EventHint) { const callbacks: ((event: Event, hint?: EventHint) => void)[] = []; const client: Client = { @@ -16,7 +12,7 @@ function testEventLogged(integration: IntegrationWithSetup, testEvent?: Event, t }, } as Client; - integration.setup(client); + integration.setup!(client); expect(callbacks.length).toEqual(1); diff --git a/packages/integrations/test/dedupe.test.ts b/packages/integrations/test/dedupe.test.ts index bb996fa45960..6d90fb5a3ae1 100644 --- a/packages/integrations/test/dedupe.test.ts +++ b/packages/integrations/test/dedupe.test.ts @@ -1,4 +1,4 @@ -import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; +import type { Client, Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; import { Dedupe, _shouldDropEvent } from '../src/dedupe'; @@ -179,25 +179,29 @@ describe('Dedupe', () => { it('ignores consecutive errors', () => { const integration = new Dedupe(); - 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 Client)).not.toBeNull(); + expect(integration.processEvent!(clone(exceptionEvent), {}, {} as Client)).toBeNull(); + expect(integration.processEvent!(clone(exceptionEvent), {}, {} as Client)).toBeNull(); }); it('ignores transactions between errors', () => { const integration = new Dedupe(); - expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull(); + expect(integration.processEvent!(clone(exceptionEvent), {}, {} as Client)).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 Client, + ), ).not.toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); + expect(integration.processEvent!(clone(exceptionEvent), {}, {} as Client)).toBeNull(); + expect(integration.processEvent!(clone(exceptionEvent), {}, {} as Client)).toBeNull(); }); }); }); diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/integrations/test/extraerrordata.test.ts index d72a43c57f8b..0ec3c8ddc35d 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/integrations/test/extraerrordata.test.ts @@ -1,10 +1,14 @@ -import type { Event as SentryEvent, ExtendedError } from '@sentry/types'; +import type { Client, Event, Event as SentryEvent, EventHint, ExtendedError } from '@sentry/types'; import { ExtraErrorData } from '../src/extraerrordata'; const extraErrorData = new ExtraErrorData(); let event: SentryEvent; +function processEvent(event: Event, hint: EventHint): Event { + return extraErrorData.processEvent!(event, hint, {} as Client) as Event; +} + describe('ExtraErrorData()', () => { beforeEach(() => { event = {}; @@ -15,7 +19,7 @@ describe('ExtraErrorData()', () => { error.baz = 42; error.foo = 'bar'; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -31,7 +35,7 @@ describe('ExtraErrorData()', () => { const error = new TypeError('foo') as ExtendedError; error.cause = new SyntaxError('bar'); - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -52,7 +56,7 @@ describe('ExtraErrorData()', () => { }, }; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -76,7 +80,7 @@ describe('ExtraErrorData()', () => { const error = new TypeError('foo') as ExtendedError; error.baz = 42; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -91,7 +95,7 @@ describe('ExtraErrorData()', () => { it('should return event if originalException is not an Error object', () => { const error = 'error message, not object'; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -99,13 +103,13 @@ describe('ExtraErrorData()', () => { }); it('should return event if there is no SentryEventHint', () => { - const enhancedEvent = extraErrorData.processEvent(event, {}); + const enhancedEvent = processEvent(event, {}); expect(enhancedEvent).toEqual(event); }); it('should return event if there is no originalException', () => { - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { // @ts-expect-error Allow event to have extra properties notOriginalException: 'fooled you', }); @@ -124,7 +128,7 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -147,7 +151,7 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -167,7 +171,7 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { + const enhancedEvent = processEvent(event, { originalException: error, }); @@ -191,9 +195,13 @@ describe('ExtraErrorData()', () => { // @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 Client, + ) as Event; expect(enhancedEvent.contexts).toEqual({ Error: { @@ -216,9 +224,13 @@ describe('ExtraErrorData()', () => { // @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 Client, + ) as Event; expect(enhancedEvent.contexts).not.toEqual({ Error: { diff --git a/packages/integrations/test/reportingobserver.test.ts b/packages/integrations/test/reportingobserver.test.ts index 275e63c82ea9..6a8e42a672f2 100644 --- a/packages/integrations/test/reportingobserver.test.ts +++ b/packages/integrations/test/reportingobserver.test.ts @@ -68,7 +68,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -83,7 +83,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -98,7 +98,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -113,7 +113,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); expect(mockObserve).toHaveBeenCalledTimes(1); }); @@ -143,7 +143,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([ @@ -160,7 +160,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([ @@ -178,7 +178,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report1 = { type: 'crash', url: 'some url 1', body: { crashId: 'id1' } } as const; @@ -196,7 +196,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([{ type: 'crash', url: 'some url' }]); @@ -210,7 +210,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -231,7 +231,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -251,7 +251,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -271,7 +271,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -290,7 +290,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { type: 'crash', url: 'some url', body: { crashId: '', reason: '' } } as const; @@ -306,7 +306,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.setup!(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -326,7 +326,7 @@ describe('ReportingObserver', () => { () => undefined, () => mockHub, ); - reportingObserverIntegration.setup(mockClient); + reportingObserverIntegration.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 7a65ff129aca..3f731f220acd 100644 --- a/packages/integrations/test/rewriteframes.test.ts +++ b/packages/integrations/test/rewriteframes.test.ts @@ -102,7 +102,7 @@ describe('RewriteFrames', () => { describe('default iteratee appends basename to `app:///` if frame starts with `/`', () => { beforeEach(() => { - rewriteFrames = new RewriteFrames(); + rewriteFrames = new RewriteFrames() as IntegrationWithProcessEvent; }); it('transforms exceptionEvent frames', () => { @@ -123,7 +123,7 @@ describe('RewriteFrames', () => { beforeEach(() => { rewriteFrames = new RewriteFrames({ prefix: 'foobar/', - }); + }) as IntegrationWithProcessEvent; }); it('transforms exceptionEvent frames', () => { @@ -135,7 +135,7 @@ describe('RewriteFrames', () => { describe('default iteratee appends basename to `app:///` if frame starts with Windows path prefix', () => { beforeEach(() => { - rewriteFrames = new RewriteFrames(); + rewriteFrames = new RewriteFrames() as IntegrationWithProcessEvent; }); it('transforms windowsExceptionEvent frames (C:\\)', () => { @@ -167,7 +167,7 @@ describe('RewriteFrames', () => { beforeEach(() => { rewriteFrames = new RewriteFrames({ root: '/www', - }); + }) as IntegrationWithProcessEvent; }); it('transforms exceptionEvent frames', () => { @@ -208,7 +208,7 @@ describe('RewriteFrames', () => { ...frame, function: 'whoops', }), - }); + }) as IntegrationWithProcessEvent; }); it('transforms exceptionEvent frames', () => { @@ -222,7 +222,7 @@ describe('RewriteFrames', () => { describe('can process events that contain multiple stacktraces', () => { it('with defaults', () => { - rewriteFrames = new RewriteFrames(); + rewriteFrames = new RewriteFrames() as IntegrationWithProcessEvent; const event = rewriteFrames.processEvent(multipleStacktracesEvent); // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); @@ -238,7 +238,7 @@ describe('RewriteFrames', () => { it('with custom root', () => { rewriteFrames = new RewriteFrames({ root: '/www', - }); + }) as IntegrationWithProcessEvent; const event = rewriteFrames.processEvent(multipleStacktracesEvent); // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); @@ -257,7 +257,7 @@ describe('RewriteFrames', () => { ...frame, function: 'whoops', }), - }); + }) as IntegrationWithProcessEvent; const event = rewriteFrames.processEvent(multipleStacktracesEvent); // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); @@ -279,7 +279,7 @@ describe('RewriteFrames', () => { describe('bails when unable to extract frames', () => { it('no exception values', () => { - rewriteFrames = new RewriteFrames({}); + rewriteFrames = new RewriteFrames({}) as IntegrationWithProcessEvent; const brokenEvent = { exception: { values: undefined, @@ -289,7 +289,7 @@ describe('RewriteFrames', () => { }); it('no frames', () => { - rewriteFrames = new RewriteFrames({}); + rewriteFrames = new RewriteFrames({}) as IntegrationWithProcessEvent; const brokenEvent = { exception: { values: [ diff --git a/packages/integrations/test/sessiontiming.test.ts b/packages/integrations/test/sessiontiming.test.ts index d1569db52095..fa131fb9ac33 100644 --- a/packages/integrations/test/sessiontiming.test.ts +++ b/packages/integrations/test/sessiontiming.test.ts @@ -1,18 +1,23 @@ +import type { Client, Event } from '@sentry/types'; import { SessionTiming } from '../src/sessiontiming'; const sessionTiming = new SessionTiming(); describe('SessionTiming', () => { it('should work as expected', () => { - const event = sessionTiming.processEvent({ - extra: { - some: 'value', + const event = sessionTiming.processEvent!( + { + extra: { + some: 'value', + }, }, - }); + {}, + {} as Client, + ) as Event; - expect(typeof event.extra['session:start']).toBe('number'); - expect(typeof event.extra['session:duration']).toBe('number'); - expect(typeof event.extra['session:end']).toBe('number'); + expect(typeof event.extra!['session:start']).toBe('number'); + expect(typeof event.extra!['session:duration']).toBe('number'); + expect(typeof event.extra!['session:end']).toBe('number'); expect((event.extra as any).some).toEqual('value'); }); }); diff --git a/packages/integrations/test/transaction.test.ts b/packages/integrations/test/transaction.test.ts index bfc6096a519e..d2a721556ebe 100644 --- a/packages/integrations/test/transaction.test.ts +++ b/packages/integrations/test/transaction.test.ts @@ -1,134 +1,156 @@ +import type { Client, Event } from '@sentry/types'; 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', - }, - ], + 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', + }, + ], + }, }, - }, - ], + ], + }, }, - }); + {}, + {} as Client, + ) as Event; 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, - }, - ], + 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, + }, + ], + }, }, - }, - ], + ], + }, }, - }); + {}, + {} as Client, + ) as Event; 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', - }, - ], + 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', + }, + ], + }, }, - }, - ], + ], + }, }, - }); + {}, + {} as Client, + ) as Event; 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, - }, - ], + const event = transaction.processEvent!( + { + exception: { + values: [ + { + stacktrace: { + frames: [ + { + filename: '/some/file1.js', + in_app: false, + }, + { + filename: '/some/file2.js', + in_app: true, + }, + ], + }, }, - }, - ], + ], + }, }, - }); + {}, + {} as Client, + ) as Event; 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, - }, - ], + const event = transaction.processEvent!( + { + exception: { + values: [ + { + stacktrace: { + frames: [ + { + filename: '/some/file1.js', + in_app: false, + }, + { + filename: '/some/file2.js', + in_app: false, + }, + ], + }, }, - }, - ], + ], + }, }, - }); + {}, + {} as Client, + ) as Event; expect(event.transaction).toBeUndefined(); }); }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 21735a64d6a1..b7d253dac6bf 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -98,7 +98,7 @@ export { createGetModuleFromFilename }; export { enableAnrDetection } from './integrations/anr/legacy'; import { Integrations as CoreIntegrations } from '@sentry/core'; -import type { Integration, IntegrationClass } from '@sentry/types'; +// import type { Integration } from '@sentry/types'; import * as Handlers from './handlers'; import * as NodeIntegrations from './integrations'; @@ -106,23 +106,7 @@ import * as TracingIntegrations from './tracing/integrations'; const INTEGRATIONS = { ...CoreIntegrations, - // This typecast is somehow needed for now, probably because of the convertIntegrationFnToClass TS shenanigans - // This is OK for now but should be resolved in v8 when we just pass the functional integrations directly - ...(NodeIntegrations as { - Console: IntegrationClass; - Http: typeof NodeIntegrations.Http; - OnUncaughtException: IntegrationClass; - OnUnhandledRejection: IntegrationClass; - Modules: IntegrationClass; - ContextLines: IntegrationClass; - Context: IntegrationClass; - RequestData: IntegrationClass; - LocalVariables: IntegrationClass; - Undici: typeof NodeIntegrations.Undici; - Spotlight: IntegrationClass; - Anr: IntegrationClass; - Hapi: IntegrationClass; - }), + ...NodeIntegrations, ...TracingIntegrations, }; diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index 1ebca2fdff57..07d55771ffa7 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -1,7 +1,7 @@ // TODO (v8): This import can be removed once we only support Node with global URL import { URL } from 'url'; import { convertIntegrationFnToClass, getCurrentScope } from '@sentry/core'; -import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; +import type { Client, Contexts, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { dynamicRequire, logger } from '@sentry/utils'; import type { Worker, WorkerOptions } from 'worker_threads'; import type { NodeClient } from '../../client'; @@ -52,7 +52,7 @@ interface InspectorApi { const INTEGRATION_NAME = 'Anr'; -const anrIntegration = ((options: Partial = {}) => { +export const anrIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -74,7 +74,9 @@ const anrIntegration = ((options: Partial = {}) => { * ANR detection requires Node 16.17.0 or later */ // eslint-disable-next-line deprecation/deprecation -export const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration); +export const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration) as IntegrationClass & { + new (options?: Partial): Integration & { setup(client: Client): void }; +}; /** * Starts the ANR worker thread diff --git a/packages/node/src/integrations/console.ts b/packages/node/src/integrations/console.ts index eb46b58adf74..7b2d82accfbb 100644 --- a/packages/node/src/integrations/console.ts +++ b/packages/node/src/integrations/console.ts @@ -5,7 +5,7 @@ import { addConsoleInstrumentationHandler, severityLevelFromString } from '@sent const INTEGRATION_NAME = 'Console'; -const consoleIntegration = (() => { +export const consoleIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index 461f85297e4b..1df36c9809f5 100644 --- a/packages/node/src/integrations/context.ts +++ b/packages/node/src/integrations/context.ts @@ -12,6 +12,8 @@ import type { CultureContext, DeviceContext, Event, + Integration, + IntegrationClass, IntegrationFn, OsContext, } from '@sentry/types'; @@ -35,7 +37,7 @@ interface ContextOptions { cloudResource?: boolean; } -const contextIntegration = ((options: ContextOptions = {}) => { +export const nodeContextIntegration = ((options: ContextOptions = {}) => { let cachedContext: Promise | undefined; const _options = { @@ -47,6 +49,7 @@ const contextIntegration = ((options: ContextOptions = {}) => { ...options, }; + /** Add contexts to the event. Caches the context so we only look it up once. */ async function addContext(event: Event): Promise { if (cachedContext === undefined) { cachedContext = _getContexts(); @@ -66,6 +69,7 @@ const contextIntegration = ((options: ContextOptions = {}) => { return event; } + /** Get the contexts from node. */ async function _getContexts(): Promise { const contexts: Contexts = {}; @@ -108,7 +112,18 @@ const contextIntegration = ((options: ContextOptions = {}) => { /** Add node modules / packages to the event */ // eslint-disable-next-line deprecation/deprecation -export const Context = convertIntegrationFnToClass(INTEGRATION_NAME, contextIntegration); +export const Context = convertIntegrationFnToClass( + INTEGRATION_NAME, + nodeContextIntegration, +) as IntegrationClass & { + new (options?: { + app?: boolean; + os?: boolean; + device?: { cpu?: boolean; memory?: boolean } | boolean; + culture?: boolean; + cloudResource?: boolean; + }): Integration; +}; /** * Updates the context with dynamic values that can change diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts index 4f7ab81f063e..961481911995 100644 --- a/packages/node/src/integrations/contextlines.ts +++ b/packages/node/src/integrations/contextlines.ts @@ -1,6 +1,6 @@ import { readFile } from 'fs'; import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; import { LRUMap, addContextToFrame } from '@sentry/utils'; const FILE_CONTENT_CACHE = new LRUMap(100); @@ -35,7 +35,7 @@ interface ContextLinesOptions { frameContextLines?: number; } -const contextLinesIntegration = ((options: ContextLinesOptions = {}) => { +export const contextLinesIntegration = ((options: ContextLinesOptions = {}) => { const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT; return { @@ -50,7 +50,10 @@ const contextLinesIntegration = ((options: ContextLinesOptions = {}) => { /** Add node modules / packages to the event */ // eslint-disable-next-line deprecation/deprecation -export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration); +export const ContextLines = convertIntegrationFnToClass( + INTEGRATION_NAME, + contextLinesIntegration, +) as IntegrationClass & { new (options?: { frameContextLines?: number }): Integration }; async function addSourceContext(event: Event, contextLines: number): Promise { // keep a lookup map of which files we've already enqueued to read, diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index 42335f7c4ce5..d53ff5512617 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -138,7 +138,7 @@ export type HapiOptions = { const INTEGRATION_NAME = 'Hapi'; -const hapiIntegration = ((options: HapiOptions = {}) => { +export const hapiIntegration = ((options: HapiOptions = {}) => { const server = options.server as undefined | Server; return { diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index e3f6d164d991..395af4c9750a 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -17,6 +17,7 @@ import type { DynamicSamplingContext, EventProcessor, Integration, + IntegrationFn, SanitizedRequestData, TracePropagationTargets, } from '@sentry/types'; @@ -29,6 +30,7 @@ import { stringMatchesSomePattern, } from '@sentry/utils'; +import type { IntegrationFnResult } from '@sentry/types'; import type { NodeClient } from '../client'; import { DEBUG_BUILD } from '../debug-build'; import { NODE_VERSION } from '../nodeVersion'; @@ -76,6 +78,10 @@ interface HttpOptions { tracing?: TracingOptions | boolean; } +export const httpIntegration = ((options: HttpOptions = {}) => { + return new Http(options) as unknown as IntegrationFnResult; +}) satisfies IntegrationFn; + /** * The http module integration instruments Node's internal http module. It creates breadcrumbs, transactions for outgoing * http requests and attaches trace data when tracing is enabled via its `tracing` option. diff --git a/packages/node/src/integrations/index-functions.ts b/packages/node/src/integrations/index-functions.ts new file mode 100644 index 000000000000..2773f9577dd6 --- /dev/null +++ b/packages/node/src/integrations/index-functions.ts @@ -0,0 +1,13 @@ +export { consoleIntegration } from './console'; +export { httpIntegration } from './http'; +export { onUncaughtExceptionIntegration } from './onuncaughtexception'; +export { onUnhandledRejectionIntegration } from './onunhandledrejection'; +export { modulesIntegration } from './modules'; +export { contextLinesIntegration } from './contextlines'; +export { nodeContextIntegration } from './context'; +export { requestDataIntegration } from '@sentry/core'; +export { localVariablesIntegration } from './local-variables'; +export { undiciIntegration } from './undici'; +export { spotlightIntegration } from './spotlight'; +export { anrIntegration } from './anr'; +export { hapiIntegration } from './hapi'; diff --git a/packages/node/src/integrations/local-variables/index.ts b/packages/node/src/integrations/local-variables/index.ts index 708b4b41ea24..b95903c385a8 100644 --- a/packages/node/src/integrations/local-variables/index.ts +++ b/packages/node/src/integrations/local-variables/index.ts @@ -1,6 +1,8 @@ -import { LocalVariablesSync } from './local-variables-sync'; +import { LocalVariablesSync, localVariablesSyncIntegration } from './local-variables-sync'; /** * Adds local variables to exception frames */ export const LocalVariables = LocalVariablesSync; + +export const localVariablesIntegration = localVariablesSyncIntegration; diff --git a/packages/node/src/integrations/local-variables/local-variables-async.ts b/packages/node/src/integrations/local-variables/local-variables-async.ts index 360d553e48a2..c5c44d6a2f1c 100644 --- a/packages/node/src/integrations/local-variables/local-variables-async.ts +++ b/packages/node/src/integrations/local-variables/local-variables-async.ts @@ -70,7 +70,7 @@ const INTEGRATION_NAME = 'LocalVariablesAsync'; /** * Adds local variables to exception frames */ -export const localVariablesAsync: IntegrationFn = (options: Options = {}) => { +export const localVariablesAsyncIntegration = ((options: Options = {}) => { const cachedFrames: LRUMap = new LRUMap(20); let rateLimiter: RateLimitIncrement | undefined; let shouldProcessEvent = false; @@ -245,10 +245,10 @@ export const localVariablesAsync: IntegrationFn = (options: Options = {}) => { return event; }, }; -}; +}) satisfies IntegrationFn; /** * Adds local variables to exception frames */ // eslint-disable-next-line deprecation/deprecation -export const LocalVariablesAsync = convertIntegrationFnToClass(INTEGRATION_NAME, localVariablesAsync); +export const LocalVariablesAsync = convertIntegrationFnToClass(INTEGRATION_NAME, localVariablesAsyncIntegration); diff --git a/packages/node/src/integrations/local-variables/local-variables-sync.ts b/packages/node/src/integrations/local-variables/local-variables-sync.ts index 45d17748ec78..335982bf2e84 100644 --- a/packages/node/src/integrations/local-variables/local-variables-sync.ts +++ b/packages/node/src/integrations/local-variables/local-variables-sync.ts @@ -213,7 +213,7 @@ const INTEGRATION_NAME = 'LocalVariables'; /** * Adds local variables to exception frames */ -export const localVariablesSync: IntegrationFn = ( +export const localVariablesSyncIntegration = (( options: Options = {}, session: DebugSession | undefined = tryNewAsyncSession(), ) => { @@ -385,10 +385,10 @@ export const localVariablesSync: IntegrationFn = ( return cachedFrames.values()[0]; }, }; -}; +}) satisfies IntegrationFn; /** * Adds local variables to exception frames */ // eslint-disable-next-line deprecation/deprecation -export const LocalVariablesSync = convertIntegrationFnToClass(INTEGRATION_NAME, localVariablesSync); +export const LocalVariablesSync = convertIntegrationFnToClass(INTEGRATION_NAME, localVariablesSyncIntegration); diff --git a/packages/node/src/integrations/modules.ts b/packages/node/src/integrations/modules.ts index 85bb792f60b2..dbecc86e7990 100644 --- a/packages/node/src/integrations/modules.ts +++ b/packages/node/src/integrations/modules.ts @@ -76,7 +76,7 @@ function _getModules(): { [key: string]: string } { return moduleCache; } -const modulesIntegration = (() => { +export const modulesIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this diff --git a/packages/node/src/integrations/onuncaughtexception.ts b/packages/node/src/integrations/onuncaughtexception.ts index bdee16d3e953..556aab3dd9a8 100644 --- a/packages/node/src/integrations/onuncaughtexception.ts +++ b/packages/node/src/integrations/onuncaughtexception.ts @@ -1,6 +1,6 @@ import { captureException, convertIntegrationFnToClass } from '@sentry/core'; import { getClient } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { NodeClient } from '../client'; @@ -40,7 +40,7 @@ interface OnUncaughtExceptionOptions { const INTEGRATION_NAME = 'OnUncaughtException'; -const onUncaughtExceptionIntegration = ((options: Partial = {}) => { +export const onUncaughtExceptionIntegration = ((options: Partial = {}) => { const _options = { exitEvenIfOtherHandlersAreRegistered: true, ...options, @@ -58,7 +58,17 @@ const onUncaughtExceptionIntegration = ((options: Partial & { + new ( + options?: Partial<{ + exitEvenIfOtherHandlersAreRegistered: boolean; + onFatalError?(this: void, firstError: Error, secondError?: Error): void; + }>, + ): Integration; +}; type ErrorHandler = { _errorHandler: boolean } & ((error: Error) => void); diff --git a/packages/node/src/integrations/onunhandledrejection.ts b/packages/node/src/integrations/onunhandledrejection.ts index 2820f8789065..5fd68b09abe2 100644 --- a/packages/node/src/integrations/onunhandledrejection.ts +++ b/packages/node/src/integrations/onunhandledrejection.ts @@ -1,5 +1,5 @@ import { captureException, convertIntegrationFnToClass, getClient } from '@sentry/core'; -import type { Client, IntegrationFn } from '@sentry/types'; +import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; import { logAndExitProcess } from './utils/errorhandling'; @@ -16,7 +16,7 @@ interface OnUnhandledRejectionOptions { const INTEGRATION_NAME = 'OnUnhandledRejection'; -const onUnhandledRejectionIntegration = ((options: Partial = {}) => { +export const onUnhandledRejectionIntegration = ((options: Partial = {}) => { const mode = options.mode || 'warn'; return { @@ -31,7 +31,10 @@ const onUnhandledRejectionIntegration = ((options: Partial & { new (options?: Partial<{ mode: UnhandledRejectionMode }>): Integration }; /** * Send an exception with reason diff --git a/packages/node/src/integrations/spotlight.ts b/packages/node/src/integrations/spotlight.ts index a248810cb2f6..8ce372338625 100644 --- a/packages/node/src/integrations/spotlight.ts +++ b/packages/node/src/integrations/spotlight.ts @@ -1,7 +1,7 @@ import * as http from 'http'; import { URL } from 'url'; import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Client, Envelope, IntegrationFn } from '@sentry/types'; +import type { Client, Envelope, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { logger, serializeEnvelope } from '@sentry/utils'; type SpotlightConnectionOptions = { @@ -14,7 +14,7 @@ type SpotlightConnectionOptions = { const INTEGRATION_NAME = 'Spotlight'; -const spotlightIntegration = ((options: Partial = {}) => { +export const spotlightIntegration = ((options: Partial = {}) => { const _options = { sidecarUrl: options.sidecarUrl || 'http://localhost:8969/stream', }; @@ -40,7 +40,16 @@ const spotlightIntegration = ((options: Partial = {} * Important: This integration only works with Node 18 or newer */ // eslint-disable-next-line deprecation/deprecation -export const Spotlight = convertIntegrationFnToClass(INTEGRATION_NAME, spotlightIntegration); +export const Spotlight = convertIntegrationFnToClass( + INTEGRATION_NAME, + spotlightIntegration, +) as IntegrationClass & { + new ( + options?: Partial<{ + sidecarUrl?: string; + }>, + ): Integration; +}; function connectToSpotlight(client: Client, options: Required): void { const spotlightUrl = parseSidecarUrl(options.sidecarUrl); diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index 252e9aee3f40..afe487598583 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -9,6 +9,8 @@ import { spanToTraceHeader, } from '@sentry/core'; import type { EventProcessor, Integration, Span } from '@sentry/types'; +import type { IntegrationFnResult } from '@sentry/types'; +import type { IntegrationFn } from '@sentry/types'; import { LRUMap, dynamicRequire, @@ -49,6 +51,10 @@ export interface UndiciOptions { shouldCreateSpanForRequest?: (url: string) => boolean; } +export const undiciIntegration = ((options: Partial = {}) => { + return new Undici(options) as unknown as IntegrationFnResult; +}) satisfies IntegrationFn; + // Please note that you cannot use `console.log` to debug the callbacks registered to the `diagnostics_channel` API. // To debug, you can use `writeFileSync` to write to a file: // https://nodejs.org/api/async_hooks.html#printing-in-asynchook-callbacks diff --git a/packages/node/test/integrations/contextlines.test.ts b/packages/node/test/integrations/contextlines.test.ts index dda78689e711..edb36acafc84 100644 --- a/packages/node/test/integrations/contextlines.test.ts +++ b/packages/node/test/integrations/contextlines.test.ts @@ -16,7 +16,7 @@ describe('ContextLines', () => { beforeEach(() => { readFileSpy = jest.spyOn(fs, 'readFile'); - contextLines = new ContextLines(); + contextLines = new ContextLines() as Integration & { processEvent: (event: Event) => Promise }; resetFileContentCache(); }); @@ -98,7 +98,9 @@ describe('ContextLines', () => { }); test('parseStack with no context', async () => { - contextLines = new ContextLines({ frameContextLines: 0 }); + contextLines = new ContextLines({ frameContextLines: 0 }) as Integration & { + processEvent: (event: Event) => Promise; + }; expect.assertions(1); const frames = parseStackFrames(defaultStackParser, new Error('test')); @@ -110,7 +112,7 @@ describe('ContextLines', () => { test('does not attempt to readfile multiple times if it fails', async () => { expect.assertions(1); - contextLines = new ContextLines({}); + contextLines = new ContextLines({}) as Integration & { processEvent: (event: Event) => Promise }; readFileSpy.mockImplementation(() => { throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'"); diff --git a/packages/node/test/integrations/spotlight.test.ts b/packages/node/test/integrations/spotlight.test.ts index 266d64b5710a..3b3ca318cc8b 100644 --- a/packages/node/test/integrations/spotlight.test.ts +++ b/packages/node/test/integrations/spotlight.test.ts @@ -30,7 +30,7 @@ describe('Spotlight', () => { }; const integration = new Spotlight(); // @ts-expect-error - this is fine in tests - integration.setup(clientWithSpy); + integration.setup!(clientWithSpy); expect(clientWithSpy.on).toHaveBeenCalledWith('beforeEnvelope', expect.any(Function)); }); @@ -51,7 +51,7 @@ describe('Spotlight', () => { const integration = new Spotlight(); // @ts-expect-error - this is fine in tests - integration.setup(clientWithSpy); + integration.setup!(clientWithSpy); const envelope = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], @@ -90,7 +90,7 @@ describe('Spotlight', () => { const integration = new Spotlight({ sidecarUrl: 'http://mylocalhost:8888/abcd' }); // @ts-expect-error - this is fine in tests - integration.setup(clientWithSpy); + integration.setup!(clientWithSpy); const envelope = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], @@ -115,7 +115,7 @@ describe('Spotlight', () => { describe('no-ops if', () => { it('an invalid URL is passed', () => { const integration = new Spotlight({ sidecarUrl: 'invalid-url' }); - integration.setup(client); + integration.setup!(client); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid sidecar URL: invalid-url')); }); @@ -125,7 +125,7 @@ describe('Spotlight', () => { // @ts-expect-error - this is fine in tests delete client.on; // @ts-expect-error - this is fine in tests - integration.setup(clientWithoutHooks); + integration.setup!(clientWithoutHooks); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining(' missing method on SDK client (`client.on`)')); }); }); @@ -135,7 +135,7 @@ describe('Spotlight', () => { process.env.NODE_ENV = 'production'; const integration = new Spotlight({ sidecarUrl: 'http://localhost:8969' }); - integration.setup(client); + integration.setup!(client); expect(loggerSpy).toHaveBeenCalledWith( expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), @@ -149,7 +149,7 @@ describe('Spotlight', () => { process.env.NODE_ENV = 'development'; const integration = new Spotlight({ sidecarUrl: 'http://localhost:8969' }); - integration.setup(client); + integration.setup!(client); expect(loggerSpy).not.toHaveBeenCalledWith( expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), @@ -165,7 +165,7 @@ describe('Spotlight', () => { delete global.process; const integration = new Spotlight({ sidecarUrl: 'http://localhost:8969' }); - integration.setup(client); + integration.setup!(client); expect(loggerSpy).not.toHaveBeenCalledWith( expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), @@ -181,7 +181,7 @@ describe('Spotlight', () => { delete process.env; const integration = new Spotlight({ sidecarUrl: 'http://localhost:8969' }); - integration.setup(client); + integration.setup!(client); expect(loggerSpy).not.toHaveBeenCalledWith( expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), diff --git a/packages/node/test/onuncaughtexception.test.ts b/packages/node/test/onuncaughtexception.test.ts index 7d2544e63f91..2e6af5b4771e 100644 --- a/packages/node/test/onuncaughtexception.test.ts +++ b/packages/node/test/onuncaughtexception.test.ts @@ -20,7 +20,7 @@ jest.mock('@sentry/core', () => { describe('uncaught exceptions', () => { test('install global listener', () => { const integration = new OnUncaughtException(); - integration.setup(client); + integration.setup!(client); expect(process.listeners('uncaughtException')).toHaveLength(1); }); diff --git a/packages/node/test/onunhandledrejection.test.ts b/packages/node/test/onunhandledrejection.test.ts index 0667cd9570b2..2fbb82d313ff 100644 --- a/packages/node/test/onunhandledrejection.test.ts +++ b/packages/node/test/onunhandledrejection.test.ts @@ -21,7 +21,7 @@ jest.mock('@sentry/core', () => { describe('unhandled promises', () => { test('install global listener', () => { const integration = new OnUnhandledRejection(); - integration.setup(client); + integration.setup!(client); expect(process.listeners('unhandledRejection')).toHaveLength(1); });