From cb84726dd1c7668103e5ff81b511f1efba231950 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 15 Jan 2024 13:26:57 +0100 Subject: [PATCH] fix: Ensure all integration classes have correct types This ensures that all integration classes have both the correct constructor options, as well as the correct instance methods. This is pretty dirty, but since we're going to remove the classes very soon in v8, IMHO that's the easiest approach to make this work properly for our users. --- .../browser/src/integrations/breadcrumbs.ts | 26 ++++++++++-- packages/browser/src/integrations/dedupe.ts | 10 +++-- .../src/integrations/globalhandlers.ts | 21 ++++++++-- .../browser/src/integrations/httpcontext.ts | 10 +++-- .../browser/src/integrations/linkederrors.ts | 10 +++-- packages/browser/src/integrations/trycatch.ts | 19 +++++++-- packages/browser/src/profiling/integration.ts | 11 +++-- .../test/unit/profiling/integration.test.ts | 1 - packages/bun/src/integrations/bunserver.ts | 4 +- packages/core/src/integration.ts | 17 +++----- .../core/src/integrations/functiontostring.ts | 11 +++-- .../core/src/integrations/inboundfilters.ts | 23 ++++++++-- .../core/src/integrations/linkederrors.ts | 10 +++-- packages/core/src/integrations/metadata.ts | 16 +++++-- packages/core/src/integrations/requestdata.ts | 42 +++++++++++++++++-- packages/core/src/metrics/integration.ts | 11 +++-- packages/core/test/lib/integration.test.ts | 2 +- packages/deno/src/integrations/context.ts | 10 +++-- .../deno/src/integrations/contextlines.ts | 11 +++-- packages/deno/src/integrations/deno-cron.ts | 6 ++- .../deno/src/integrations/globalhandlers.ts | 19 +++++++-- .../deno/src/integrations/normalizepaths.ts | 12 ++++-- packages/integrations/src/captureconsole.ts | 9 +++- packages/integrations/src/contextlines.ts | 10 +++-- packages/integrations/src/debug.ts | 11 ++++- packages/integrations/src/dedupe.ts | 6 ++- packages/integrations/src/extraerrordata.ts | 22 +++++++++- packages/integrations/src/httpclient.ts | 18 +++++++- .../integrations/src/reportingobserver.ts | 12 +++++- packages/integrations/src/rewriteframes.ts | 11 ++++- packages/integrations/src/sessiontiming.ts | 7 +++- packages/integrations/src/transaction.ts | 6 ++- .../integrations/test/sessiontiming.test.ts | 8 ++-- packages/node/src/index.ts | 19 +-------- packages/node/src/integrations/anr/index.ts | 8 +++- packages/node/src/integrations/console.ts | 6 ++- packages/node/src/integrations/context.ts | 18 +++++++- .../node/src/integrations/contextlines.ts | 6 ++- .../local-variables/local-variables-async.ts | 11 +++-- .../local-variables/local-variables-sync.ts | 13 ++++-- packages/node/src/integrations/modules.ts | 6 ++- .../src/integrations/onuncaughtexception.ts | 14 ++++++- .../src/integrations/onunhandledrejection.ts | 9 +++- packages/node/src/integrations/spotlight.ts | 12 +++++- 44 files changed, 391 insertions(+), 153 deletions(-) diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 9799e6e73b4f..485b19d3b711 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 = {}) => { +const breadcrumbsIntegration = ((options: Partial = {}) => { const _options = { console: true, dom: true, @@ -91,13 +93,31 @@ const breadcrumbsIntegration: IntegrationFn = (options: Partial void } +> & { + 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..394d5c5ae1e3 100644 --- a/packages/browser/src/integrations/dedupe.ts +++ b/packages/browser/src/integrations/dedupe.ts @@ -1,12 +1,12 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, Exception, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; const INTEGRATION_NAME = 'Dedupe'; -const dedupeIntegration: IntegrationFn = () => { +const dedupeIntegration = (() => { let previousEvent: Event | undefined; return { @@ -31,11 +31,13 @@ const dedupeIntegration: IntegrationFn = () => { return (previousEvent = currentEvent); }, }; -}; +}) satisfies IntegrationFn; /** Deduplication filter */ // eslint-disable-next-line deprecation/deprecation -export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration); +export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration) as IntegrationClass< + Integration & { processEvent: (event: Event) => Event } +>; function _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean { if (!previousEvent) { diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index e829b6f92845..01ef16a02851 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 = {}) => { +const globalHandlersIntegration = ((options: Partial = {}) => { const _options = { onerror: true, onunhandledrejection: true, @@ -45,11 +53,16 @@ const globalHandlersIntegrations: IntegrationFn = (options: Partial void }> & { + 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..569eeffab45c 100644 --- a/packages/browser/src/integrations/httpcontext.ts +++ b/packages/browser/src/integrations/httpcontext.ts @@ -1,11 +1,11 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { WINDOW } from '../helpers'; const INTEGRATION_NAME = 'HttpContext'; -const httpContextIntegration: IntegrationFn = () => { +const httpContextIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -31,8 +31,10 @@ const httpContextIntegration: IntegrationFn = () => { event.request = request; }, }; -}; +}) satisfies IntegrationFn; /** HttpContext integration collects information about HTTP request headers */ // eslint-disable-next-line deprecation/deprecation -export const HttpContext = convertIntegrationFnToClass(INTEGRATION_NAME, httpContextIntegration); +export const HttpContext = convertIntegrationFnToClass(INTEGRATION_NAME, httpContextIntegration) as IntegrationClass< + Integration & { preprocessEvent: (event: Event) => void } +>; diff --git a/packages/browser/src/integrations/linkederrors.ts b/packages/browser/src/integrations/linkederrors.ts index bb351bcdc3c6..8a166e7667d9 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 { Client, Event, EventHint, 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 = {}) => { +const linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { const limit = options.limit || DEFAULT_LIMIT; const key = options.key || DEFAULT_KEY; @@ -36,8 +36,10 @@ 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< + Integration & { preprocessEvent: (event: Event, hint: EventHint, client: Client) => void } +> & { 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..2f5f06592805 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 = {}) => { +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..a3af7744c4e4 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass, getCurrentScope } from '@sentry/core'; -import type { EventEnvelope, IntegrationFn, Transaction } from '@sentry/types'; +import type { Client, EventEnvelope, Integration, IntegrationClass, IntegrationFn, Transaction } from '@sentry/types'; import type { Profile } from '@sentry/types/src/profiling'; import { logger } from '@sentry/utils'; @@ -18,7 +18,7 @@ import { const INTEGRATION_NAME = 'BrowserProfiling'; -const browserProfilingIntegration: IntegrationFn = () => { +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"] @@ -112,4 +112,7 @@ const browserProfilingIntegration: IntegrationFn = () => { * @experimental */ // eslint-disable-next-line deprecation/deprecation -export const BrowserProfilingIntegration = convertIntegrationFnToClass(INTEGRATION_NAME, browserProfilingIntegration); +export const BrowserProfilingIntegration = convertIntegrationFnToClass( + INTEGRATION_NAME, + browserProfilingIntegration, +) as IntegrationClass void }>; 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/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index fec3aae439af..fb12cf94432b 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 = () => { +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/integration.ts b/packages/core/src/integration.ts index 4b4a3177c428..6b63b6fb7f85 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'; @@ -169,18 +169,11 @@ function findIndex(arr: T[], callback: (item: T) => boolean): number { export function convertIntegrationFnToClass( name: string, fn: Fn, -): Integration & { - id: string; - new (...args: Parameters): Integration & ReturnType; -} { +): IntegrationClass { return Object.assign( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function ConvertedIntegration(...rest: Parameters) { - return fn(...rest); + function ConvertedIntegration(...args: Parameters): Integration { + return fn(...args); }, { 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..112899d525ac 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -1,4 +1,4 @@ -import type { IntegrationFn, WrappedFunction } from '@sentry/types'; +import type { Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types'; import { getOriginalFunction } from '@sentry/utils'; import { convertIntegrationFnToClass } from '../integration'; @@ -6,7 +6,7 @@ let originalFunctionToString: () => void; const INTEGRATION_NAME = 'FunctionToString'; -const functionToStringIntegration: IntegrationFn = () => { +const functionToStringIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { @@ -26,8 +26,11 @@ const functionToStringIntegration: IntegrationFn = () => { } }, }; -}; +}) satisfies IntegrationFn; /** Patch toString calls to return proper name for wrapped functions */ // eslint-disable-next-line deprecation/deprecation -export const FunctionToString = convertIntegrationFnToClass(INTEGRATION_NAME, functionToStringIntegration); +export const FunctionToString = convertIntegrationFnToClass( + INTEGRATION_NAME, + functionToStringIntegration, +) as IntegrationClass void }>; diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 6d2ec6dfc799..42cbfbae7e46 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 { Client, Event, EventHint, 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) => { +const inboundFiltersIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -41,11 +41,26 @@ const inboundFiltersIntegration: IntegrationFn = (options: Partial void }> & { + 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..1b0b5135a2f1 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 { Client, Event, EventHint, 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 = {}) => { +const linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { const limit = options.limit || DEFAULT_LIMIT; const key = options.key || DEFAULT_KEY; @@ -34,8 +34,10 @@ 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< + Integration & { preprocessEvent: (event: Event, hint: EventHint, client: Client) => void } +> & { 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..652aa081529c 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -1,4 +1,4 @@ -import type { EventItem, IntegrationFn } from '@sentry/types'; +import type { Client, Event, EventHint, EventItem, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { forEachEnvelopeItem } from '@sentry/utils'; import { convertIntegrationFnToClass } from '../integration'; @@ -6,7 +6,7 @@ import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metad const INTEGRATION_NAME = 'ModuleMetadata'; -const moduleMetadataIntegration: IntegrationFn = () => { +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. @@ -49,4 +49,12 @@ const moduleMetadataIntegration: IntegrationFn = () => { * our sources */ // eslint-disable-next-line deprecation/deprecation -export const ModuleMetadata = convertIntegrationFnToClass(INTEGRATION_NAME, moduleMetadataIntegration); +export const ModuleMetadata = convertIntegrationFnToClass( + INTEGRATION_NAME, + moduleMetadataIntegration, +) as IntegrationClass< + Integration & { + setup: (client: Client) => void; + processEvent: (event: Event, hint: EventHint, client: Client) => Event; + } +>; diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts index 76d29b4751b7..80d587e09a57 100644 --- a/packages/core/src/integrations/requestdata.ts +++ b/packages/core/src/integrations/requestdata.ts @@ -1,4 +1,12 @@ -import type { Client, IntegrationFn, Transaction } from '@sentry/types'; +import type { + Client, + Event, + EventHint, + 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 +55,7 @@ const DEFAULT_OPTIONS = { const INTEGRATION_NAME = 'RequestData'; -const requestDataIntegration: IntegrationFn = (options: RequestDataIntegrationOptions = {}) => { +const requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) => { const _addRequestData = addRequestDataToEvent; const _options: Required = { ...DEFAULT_OPTIONS, @@ -129,12 +137,38 @@ 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< + Integration & { processEvent: (event: Event, hint: EventHint, client: Client) => Event } +> & { + 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/integration.ts b/packages/core/src/metrics/integration.ts index 5cadf47ce3f2..d4c77c6b61fe 100644 --- a/packages/core/src/metrics/integration.ts +++ b/packages/core/src/metrics/integration.ts @@ -1,11 +1,11 @@ -import type { ClientOptions, IntegrationFn } from '@sentry/types'; +import type { Client, ClientOptions, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import type { BaseClient } from '../baseclient'; import { convertIntegrationFnToClass } from '../integration'; import { BrowserMetricsAggregator } from './browser-aggregator'; const INTEGRATION_NAME = 'MetricsAggregator'; -const metricsAggregatorIntegration: IntegrationFn = () => { +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. @@ -22,4 +22,7 @@ const metricsAggregatorIntegration: IntegrationFn = () => { * @experimental This API is experimental and might having breaking changes in the future. */ // eslint-disable-next-line deprecation/deprecation -export const MetricsAggregator = convertIntegrationFnToClass(INTEGRATION_NAME, metricsAggregatorIntegration); +export const MetricsAggregator = convertIntegrationFnToClass( + INTEGRATION_NAME, + metricsAggregatorIntegration, +) as IntegrationClass void }>; 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/deno/src/integrations/context.ts b/packages/deno/src/integrations/context.ts index 426d296f6efc..199da80d9b4b 100644 --- a/packages/deno/src/integrations/context.ts +++ b/packages/deno/src/integrations/context.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, IntegrationFn } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; const INTEGRATION_NAME = 'DenoContext'; @@ -52,7 +52,7 @@ async function addDenoRuntimeContext(event: Event): Promise { return event; } -const denoContextIntegration: IntegrationFn = () => { +const denoContextIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this @@ -61,8 +61,10 @@ const denoContextIntegration: IntegrationFn = () => { return addDenoRuntimeContext(event); }, }; -}; +}) satisfies IntegrationFn; /** Adds Deno context to events. */ // eslint-disable-next-line deprecation/deprecation -export const DenoContext = convertIntegrationFnToClass(INTEGRATION_NAME, denoContextIntegration); +export const DenoContext = convertIntegrationFnToClass(INTEGRATION_NAME, denoContextIntegration) as IntegrationClass< + Integration & { processEvent: (event: Event) => Promise } +>; diff --git a/packages/deno/src/integrations/contextlines.ts b/packages/deno/src/integrations/contextlines.ts index f3099151908a..1b3b413699f6 100644 --- a/packages/deno/src/integrations/contextlines.ts +++ b/packages/deno/src/integrations/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 { LRUMap, addContextToFrame } from '@sentry/utils'; const INTEGRATION_NAME = 'ContextLines'; @@ -47,7 +47,7 @@ interface ContextLinesOptions { frameContextLines?: number; } -const denoContextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = {}) => { +const denoContextLinesIntegration = ((options: ContextLinesOptions = {}) => { const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT; return { @@ -58,11 +58,14 @@ const denoContextLinesIntegration: IntegrationFn = (options: ContextLinesOptions return addSourceContext(event, contextLines); }, }; -}; +}) satisfies IntegrationFn; /** Add node modules / packages to the event */ // eslint-disable-next-line deprecation/deprecation -export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, denoContextLinesIntegration); +export const ContextLines = convertIntegrationFnToClass( + INTEGRATION_NAME, + denoContextLinesIntegration, +) as IntegrationClass Promise }>; /** Processes an event and adds context lines */ async function addSourceContext(event: Event, contextLines: number): Promise { diff --git a/packages/deno/src/integrations/deno-cron.ts b/packages/deno/src/integrations/deno-cron.ts index 73c1bc1954fb..3b337b004405 100644 --- a/packages/deno/src/integrations/deno-cron.ts +++ b/packages/deno/src/integrations/deno-cron.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass, getClient, withMonitor } from '@sentry/core'; -import type { Client, IntegrationFn } from '@sentry/types'; +import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { parseScheduleToString } from './deno-cron-format'; type CronOptions = { backoffSchedule?: number[]; signal?: AbortSignal }; @@ -62,4 +62,6 @@ const denoCronIntegration = (() => { /** Instruments Deno.cron to automatically capture cron check-ins */ // eslint-disable-next-line deprecation/deprecation -export const DenoCron = convertIntegrationFnToClass(INTEGRATION_NAME, denoCronIntegration); +export const DenoCron = convertIntegrationFnToClass(INTEGRATION_NAME, denoCronIntegration) as IntegrationClass< + Integration & { setup: (client: Client) => void } +>; diff --git a/packages/deno/src/integrations/globalhandlers.ts b/packages/deno/src/integrations/globalhandlers.ts index 05b46b58c7a9..895c52ee59e4 100644 --- a/packages/deno/src/integrations/globalhandlers.ts +++ b/packages/deno/src/integrations/globalhandlers.ts @@ -3,7 +3,15 @@ import { convertIntegrationFnToClass } from '@sentry/core'; import { captureEvent } from '@sentry/core'; import { getClient } from '@sentry/core'; import { flush } 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 { eventFromUnknownInput, isPrimitive } from '@sentry/utils'; type GlobalHandlersIntegrationsOptionKeys = 'error' | 'unhandledrejection'; @@ -13,7 +21,7 @@ type GlobalHandlersIntegrations = Record { +const globalHandlersIntegration = ((options?: GlobalHandlersIntegrations) => { const _options = { error: true, unhandledrejection: true, @@ -33,11 +41,14 @@ const globalHandlersIntegration: IntegrationFn = (options?: GlobalHandlersIntegr } }, }; -}; +}) satisfies IntegrationFn; /** Global handlers */ // eslint-disable-next-line deprecation/deprecation -export const GlobalHandlers = convertIntegrationFnToClass(INTEGRATION_NAME, globalHandlersIntegration); +export const GlobalHandlers = convertIntegrationFnToClass( + INTEGRATION_NAME, + globalHandlersIntegration, +) as IntegrationClass void }>; function installGlobalErrorHandler(client: Client): void { globalThis.addEventListener('error', data => { diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index 39a3bab5a1c5..68ba3986e805 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { createStackParser, dirname, nodeStackLineParser } from '@sentry/utils'; const INTEGRATION_NAME = 'NormalizePaths'; @@ -55,10 +55,11 @@ function getCwd(): string | undefined { return undefined; } -const normalizePathsIntegration: IntegrationFn = () => { +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,8 +96,11 @@ const normalizePathsIntegration: IntegrationFn = () => { return event; }, }; -}; +}) satisfies IntegrationFn; /** Normalises paths to the app root directory. */ // eslint-disable-next-line deprecation/deprecation -export const NormalizePaths = convertIntegrationFnToClass(INTEGRATION_NAME, normalizePathsIntegration); +export const NormalizePaths = convertIntegrationFnToClass( + INTEGRATION_NAME, + normalizePathsIntegration, +) as IntegrationClass Event }>; diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index 4d9aa04ce116..ece89c0ac38c 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, Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { CONSOLE_LEVELS, GLOBAL_OBJ, @@ -40,7 +40,12 @@ 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 void }> & { + 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..86647fd8a769 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 = {}) => { +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,9 @@ 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< + Integration & { processEvent: (event: Event) => Event } +> & { 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..159d8a462bea 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 { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; const INTEGRATION_NAME = 'Debug'; @@ -58,4 +58,11 @@ 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< + Integration & { setup: (client: Client) => void } +> & { + new (options?: { + stringify?: boolean; + debugger?: boolean; + }): Integration; +}; diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index 97a47193b19a..996fd6161f9f 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, Exception, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; @@ -35,7 +35,9 @@ const dedupeIntegration = (() => { /** Deduplication filter */ // eslint-disable-next-line deprecation/deprecation -export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration); +export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration) as IntegrationClass< + Integration & { processEvent: (event: Event) => Event } +>; /** only exported for tests. */ export function _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean { diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 6b340a1283bc..41c4668f49de 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'; @@ -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 Event }> & { + 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..4368beab8364 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, @@ -58,7 +65,14 @@ 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< + Integration & { setup: (client: Client) => void } +> & { + new (options?: { + failedRequestStatusCodes: HttpStatusCodeRange[]; + failedRequestTargets: HttpRequestTarget[]; + }): Integration; +}; /** * Interceptor function for fetch requests diff --git a/packages/integrations/src/reportingobserver.ts b/packages/integrations/src/reportingobserver.ts index cd8c28b54f8c..39c7da5ce97c 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; @@ -51,6 +51,7 @@ const SETUP_CLIENTS = new WeakMap(); 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 void }> & { + new (options?: { + types?: ReportTypes[]; + }): Integration; +}; diff --git a/packages/integrations/src/rewriteframes.ts b/packages/integrations/src/rewriteframes.ts index 7bb5957607b2..31e9691df261 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; @@ -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 Event }> & { + 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..0a316ca02381 100644 --- a/packages/integrations/src/sessiontiming.ts +++ b/packages/integrations/src/sessiontiming.ts @@ -1,5 +1,5 @@ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; const INTEGRATION_NAME = 'SessionTiming'; @@ -28,4 +28,7 @@ const sessionTimingIntegration = (() => { /** This function adds duration since Sentry was initialized till the time event was sent */ // eslint-disable-next-line deprecation/deprecation -export const SessionTiming = convertIntegrationFnToClass(INTEGRATION_NAME, sessionTimingIntegration); +export const SessionTiming = convertIntegrationFnToClass( + INTEGRATION_NAME, + sessionTimingIntegration, +) as IntegrationClass Event }>; diff --git a/packages/integrations/src/transaction.ts b/packages/integrations/src/transaction.ts index ce1701f041a4..5d6d0cd595e9 100644 --- a/packages/integrations/src/transaction.ts +++ b/packages/integrations/src/transaction.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'; const INTEGRATION_NAME = 'Transaction'; @@ -31,7 +31,9 @@ const transactionIntegration = (() => { * @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, transactionIntegration) as IntegrationClass< + Integration & { processEvent: (event: Event) => Event } +>; function _getFramesFromEvent(event: Event): StackFrame[] { const exception = event.exception && event.exception.values && event.exception.values[0]; diff --git a/packages/integrations/test/sessiontiming.test.ts b/packages/integrations/test/sessiontiming.test.ts index d1569db52095..3eba2b906134 100644 --- a/packages/integrations/test/sessiontiming.test.ts +++ b/packages/integrations/test/sessiontiming.test.ts @@ -10,9 +10,9 @@ describe('SessionTiming', () => { }, }); - 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'); + 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?.some).toEqual('value'); }); }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 6d856d14fc39..a6c5ba4ea1fd 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -99,7 +99,6 @@ export { createGetModuleFromFilename }; export { enableAnrDetection } from './integrations/anr/legacy'; import { Integrations as CoreIntegrations } from '@sentry/core'; -import type { Integration, IntegrationClass } from '@sentry/types'; import * as Handlers from './handlers'; import * as NodeIntegrations from './integrations'; @@ -107,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..e444427f2e47 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'; @@ -74,7 +74,11 @@ 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< + Integration & { setup: (client: NodeClient) => void } +> & { + 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..1d4e0182e59a 100644 --- a/packages/node/src/integrations/console.ts +++ b/packages/node/src/integrations/console.ts @@ -1,6 +1,6 @@ import * as util from 'util'; import { addBreadcrumb, convertIntegrationFnToClass, getClient } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { addConsoleInstrumentationHandler, severityLevelFromString } from '@sentry/utils'; const INTEGRATION_NAME = 'Console'; @@ -34,4 +34,6 @@ const consoleIntegration = (() => { /** Console module integration */ // eslint-disable-next-line deprecation/deprecation -export const Console = convertIntegrationFnToClass(INTEGRATION_NAME, consoleIntegration); +export const Console = convertIntegrationFnToClass(INTEGRATION_NAME, consoleIntegration) as IntegrationClass< + Integration & { setup: (client: Client) => void } +>; diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index 461f85297e4b..058ce40b4c11 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 = {}) => { +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,17 @@ 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< + Integration & { processEvent: (event: Event) => Promise } +> & { + 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..eccc80f7527a 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); @@ -50,7 +50,9 @@ 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< + Integration & { processEvent: (event: Event) => Promise } +> & { 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/local-variables/local-variables-async.ts b/packages/node/src/integrations/local-variables/local-variables-async.ts index 360d553e48a2..7e393dced9e0 100644 --- a/packages/node/src/integrations/local-variables/local-variables-async.ts +++ b/packages/node/src/integrations/local-variables/local-variables-async.ts @@ -1,6 +1,6 @@ import type { Session } from 'node:inspector/promises'; import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, Exception, IntegrationFn, StackParser } from '@sentry/types'; +import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types'; import { LRUMap, dynamicRequire, logger } from '@sentry/utils'; import type { Debugger, InspectorNotification, Runtime } from 'inspector'; @@ -70,7 +70,7 @@ const INTEGRATION_NAME = 'LocalVariablesAsync'; /** * Adds local variables to exception frames */ -export const localVariablesAsync: IntegrationFn = (options: Options = {}) => { +const localVariablesAsyncIntegration = ((options: Options = {}) => { const cachedFrames: LRUMap = new LRUMap(20); let rateLimiter: RateLimitIncrement | undefined; let shouldProcessEvent = false; @@ -245,10 +245,13 @@ 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, +) as IntegrationClass Event; setup: (client: NodeClient) => void }>; 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..bfe255002975 100644 --- a/packages/node/src/integrations/local-variables/local-variables-sync.ts +++ b/packages/node/src/integrations/local-variables/local-variables-sync.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, Exception, IntegrationFn, StackParser } from '@sentry/types'; +import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types'; import { LRUMap, logger } from '@sentry/utils'; import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector'; import type { NodeClient } from '../../client'; @@ -213,7 +213,7 @@ const INTEGRATION_NAME = 'LocalVariables'; /** * Adds local variables to exception frames */ -export const localVariablesSync: IntegrationFn = ( +const localVariablesSyncIntegration = (( options: Options = {}, session: DebugSession | undefined = tryNewAsyncSession(), ) => { @@ -385,10 +385,15 @@ 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, +) as IntegrationClass Event; setup: (client: NodeClient) => void }> & { + new (options?: Options, session?: DebugSession): Integration; +}; diff --git a/packages/node/src/integrations/modules.ts b/packages/node/src/integrations/modules.ts index 85bb792f60b2..e21a92e770d7 100644 --- a/packages/node/src/integrations/modules.ts +++ b/packages/node/src/integrations/modules.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'fs'; import { dirname, join } from 'path'; import { convertIntegrationFnToClass } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; let moduleCache: { [key: string]: string }; @@ -94,4 +94,6 @@ const modulesIntegration = (() => { /** Add node modules / packages to the event */ // eslint-disable-next-line deprecation/deprecation -export const Modules = convertIntegrationFnToClass(INTEGRATION_NAME, modulesIntegration); +export const Modules = convertIntegrationFnToClass(INTEGRATION_NAME, modulesIntegration) as IntegrationClass< + Integration & { processEvent: (event: Event) => Event } +>; diff --git a/packages/node/src/integrations/onuncaughtexception.ts b/packages/node/src/integrations/onuncaughtexception.ts index bdee16d3e953..a3346f6153d5 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'; @@ -58,7 +58,17 @@ const onUncaughtExceptionIntegration = ((options: Partial void }> & { + 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..45fa5a61ca57 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'; @@ -31,7 +31,12 @@ const onUnhandledRejectionIntegration = ((options: Partial void }> & { + 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..f6e5c1e45adc 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 = { @@ -40,7 +40,15 @@ 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< + Integration & { setup: (client: Client) => void } +> & { + new ( + options?: Partial<{ + sidecarUrl?: string; + }>, + ): Integration; +}; function connectToSpotlight(client: Client, options: Required): void { const spotlightUrl = parseSidecarUrl(options.sidecarUrl);