diff --git a/packages/core/src/carrier.ts b/packages/core/src/carrier.ts index d053234b4929..1879dc47f2d4 100644 --- a/packages/core/src/carrier.ts +++ b/packages/core/src/carrier.ts @@ -1,6 +1,7 @@ import type { AsyncContextStack } from './asyncContext/stackStrategy'; import type { AsyncContextStrategy } from './asyncContext/types'; -import type { Client, Integration, MetricsAggregator, Scope } from './types-hoist'; +import type { Client, MetricsAggregator, Scope } from './types-hoist'; +import type { Logger } from './utils-hoist/logger'; import { SDK_VERSION } from './utils-hoist/version'; import { GLOBAL_OBJ } from './utils-hoist/worldwide'; @@ -16,7 +17,7 @@ type VersionedCarrier = { version?: string; } & Record, SentryCarrier>; -interface SentryCarrier { +export interface SentryCarrier { acs?: AsyncContextStrategy; stack?: AsyncContextStack; @@ -24,13 +25,12 @@ interface SentryCarrier { defaultIsolationScope?: Scope; defaultCurrentScope?: Scope; globalMetricsAggregators?: WeakMap | undefined; + logger?: Logger; - // TODO(v9): Remove these properties - they are no longer used and were left over in v8 - integrations?: Integration[]; - extensions?: { - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: Function; - }; + /** Overwrites TextEncoder used in `@sentry/core`, need for `react-native@0.73` and older */ + encodePolyfill?: (input: string) => Uint8Array; + /** Overwrites TextDecoder used in `@sentry/core`, need for `react-native@0.73` and older */ + decodePolyfill?: (input: Uint8Array) => string; } /** @@ -57,3 +57,25 @@ export function getSentryCarrier(carrier: Carrier): SentryCarrier { // rather than what's set in .version so that "this" SDK always gets its carrier return (__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {}); } + +/** + * Returns a global singleton contained in the global `__SENTRY__[]` object. + * + * If the singleton doesn't already exist in `__SENTRY__`, it will be created using the given factory + * function and added to the `__SENTRY__` object. + * + * @param name name of the global singleton on __SENTRY__ + * @param creator creator Factory function to create the singleton if it doesn't already exist on `__SENTRY__` + * @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `GLOBAL_OBJ`'s return value + * @returns the singleton + */ +export function getGlobalSingleton( + name: Prop, + creator: () => NonNullable, + obj = GLOBAL_OBJ, +): NonNullable { + const __SENTRY__ = (obj.__SENTRY__ = obj.__SENTRY__ || {}); + const carrier = (__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {}); + // Note: We do not want to set `carrier.version` here, as this may be called before any `init` is called, e.g. for the default scopes + return carrier[name] || (carrier[name] = creator()); +} diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index 85b148738467..b339a9f6d4cf 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -1,9 +1,9 @@ import { getAsyncContextStrategy } from './asyncContext'; import { getMainCarrier } from './carrier'; +import { getGlobalSingleton } from './carrier'; import { Scope as ScopeClass } from './scope'; import type { Client, Scope, TraceContext } from './types-hoist'; import { dropUndefinedKeys } from './utils-hoist/object'; -import { getGlobalSingleton } from './utils-hoist/worldwide'; /** * Get the currently active scope. diff --git a/packages/core/src/defaultScopes.ts b/packages/core/src/defaultScopes.ts index c9fb32c2049e..581eef68aff1 100644 --- a/packages/core/src/defaultScopes.ts +++ b/packages/core/src/defaultScopes.ts @@ -1,6 +1,6 @@ +import { getGlobalSingleton } from './carrier'; import { Scope as ScopeClass } from './scope'; import type { Scope } from './types-hoist'; -import { getGlobalSingleton } from './utils-hoist/worldwide'; /** Get the default current scope. */ export function getDefaultCurrentScope(): Scope { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 77259d2434d4..efaa6a12a675 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -45,7 +45,7 @@ export { getDefaultIsolationScope, } from './defaultScopes'; export { setAsyncContextStrategy } from './asyncContext'; -export { getMainCarrier } from './carrier'; +export { getGlobalSingleton, getMainCarrier } from './carrier'; export { makeSession, closeSession, updateSession } from './session'; // eslint-disable-next-line deprecation/deprecation export { SessionFlusher } from './sessionflusher'; diff --git a/packages/core/src/metrics/exports.ts b/packages/core/src/metrics/exports.ts index 00f100bcaeb2..03d2ef90efe8 100644 --- a/packages/core/src/metrics/exports.ts +++ b/packages/core/src/metrics/exports.ts @@ -1,10 +1,10 @@ +import { getGlobalSingleton } from '../carrier'; import { getClient } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; import { startSpanManual } from '../tracing'; import type { Client, DurationUnit, MetricData, MetricsAggregator as MetricsAggregatorInterface } from '../types-hoist'; import { logger } from '../utils-hoist/logger'; import { timestampInSeconds } from '../utils-hoist/time'; -import { getGlobalSingleton } from '../utils-hoist/worldwide'; import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { getActiveSpan, getRootSpan, spanToJSON } from '../utils/spanUtils'; import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_METRIC_TYPE } from './constants'; @@ -23,9 +23,9 @@ function getMetricsAggregatorForClient( client: Client, Aggregator: MetricsAggregatorConstructor, ): MetricsAggregatorInterface { - const globalMetricsAggregators = getGlobalSingleton>( + const globalMetricsAggregators = getGlobalSingleton( 'globalMetricsAggregators', - () => new WeakMap(), + () => new WeakMap(), ); const aggregator = globalMetricsAggregators.get(client); diff --git a/packages/core/src/utils-hoist/envelope.ts b/packages/core/src/utils-hoist/envelope.ts index be640b90ad4f..52fb7e175070 100644 --- a/packages/core/src/utils-hoist/envelope.ts +++ b/packages/core/src/utils-hoist/envelope.ts @@ -1,3 +1,4 @@ +import { getSentryCarrier } from '../carrier'; import type { Attachment, AttachmentItem, @@ -74,18 +75,16 @@ export function envelopeContainsItemType(envelope: Envelope, types: EnvelopeItem * Encode a string to UTF8 array. */ function encodeUTF8(input: string): Uint8Array { - return GLOBAL_OBJ.__SENTRY__ && GLOBAL_OBJ.__SENTRY__.encodePolyfill - ? GLOBAL_OBJ.__SENTRY__.encodePolyfill(input) - : new TextEncoder().encode(input); + const carrier = getSentryCarrier(GLOBAL_OBJ); + return carrier.encodePolyfill ? carrier.encodePolyfill(input) : new TextEncoder().encode(input); } /** * Decode a UTF8 array to string. */ function decodeUTF8(input: Uint8Array): string { - return GLOBAL_OBJ.__SENTRY__ && GLOBAL_OBJ.__SENTRY__.decodePolyfill - ? GLOBAL_OBJ.__SENTRY__.decodePolyfill(input) - : new TextDecoder().decode(input); + const carrier = getSentryCarrier(GLOBAL_OBJ); + return carrier.decodePolyfill ? carrier.decodePolyfill(input) : new TextDecoder().decode(input); } /** diff --git a/packages/core/src/utils-hoist/index.ts b/packages/core/src/utils-hoist/index.ts index b643f2e46d84..28a981be7bb4 100644 --- a/packages/core/src/utils-hoist/index.ts +++ b/packages/core/src/utils-hoist/index.ts @@ -5,7 +5,7 @@ export { getBreadcrumbLogLevelFromHttpStatusCode } from './breadcrumb-log-level' export { getComponentName, getDomElement, getLocationHref, htmlTreeAsString } from './browser'; export { dsnFromString, dsnToString, makeDsn } from './dsn'; export { SentryError } from './error'; -export { GLOBAL_OBJ, getGlobalSingleton } from './worldwide'; +export { GLOBAL_OBJ } from './worldwide'; export type { InternalGlobal } from './worldwide'; export { addConsoleInstrumentationHandler } from './instrument/console'; export { addFetchEndInstrumentationHandler, addFetchInstrumentationHandler } from './instrument/fetch'; diff --git a/packages/core/src/utils-hoist/logger.ts b/packages/core/src/utils-hoist/logger.ts index 90d306f11434..ee642d44582d 100644 --- a/packages/core/src/utils-hoist/logger.ts +++ b/packages/core/src/utils-hoist/logger.ts @@ -1,7 +1,7 @@ +import { getGlobalSingleton } from '../carrier'; import type { ConsoleLevel } from '../types-hoist'; - import { DEBUG_BUILD } from './debug-build'; -import { GLOBAL_OBJ, getGlobalSingleton } from './worldwide'; +import { GLOBAL_OBJ } from './worldwide'; /** Prefix for logging strings */ const PREFIX = 'Sentry Logger '; @@ -24,7 +24,7 @@ export const originalConsoleMethods: { [key in ConsoleLevel]?: (...args: unknown[]) => void; } = {}; -/** JSDoc */ +/** A Sentry Logger instance. */ export interface Logger extends LoggerConsoleMethods { disable(): void; enable(): void; diff --git a/packages/core/src/utils-hoist/worldwide.ts b/packages/core/src/utils-hoist/worldwide.ts index 92018731ff4f..cd88db09942c 100644 --- a/packages/core/src/utils-hoist/worldwide.ts +++ b/packages/core/src/utils-hoist/worldwide.ts @@ -12,40 +12,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Client, MetricsAggregator, Scope } from '../types-hoist'; - +import type { Carrier } from '../carrier'; import type { SdkSource } from './env'; -import type { logger } from './logger'; -import { SDK_VERSION } from './version'; - -interface SentryCarrier { - acs?: any; - stack?: any; - - globalScope?: Scope; - defaultIsolationScope?: Scope; - defaultCurrentScope?: Scope; - globalMetricsAggregators?: WeakMap | undefined; - logger?: typeof logger; - - /** Overwrites TextEncoder used in `@sentry/core`, need for `react-native@0.73` and older */ - encodePolyfill?: (input: string) => Uint8Array; - /** Overwrites TextDecoder used in `@sentry/core`, need for `react-native@0.73` and older */ - decodePolyfill?: (input: Uint8Array) => string; -} - -// TODO(v9): Clean up or remove this type -type BackwardsCompatibleSentryCarrier = SentryCarrier & { - // pre-v7 hub (replaced by .stack) - hub: any; - integrations?: any[]; - logger: any; - extensions?: { - /** Extension methods for the hub, which are bound to the current Hub instance */ - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: Function; - }; -}; /** Internal global with common properties and Sentry extensions */ export type InternalGlobal = { @@ -73,9 +41,6 @@ export type InternalGlobal = { * file. */ _sentryDebugIds?: Record; - __SENTRY__: Record, SentryCarrier> & { - version?: string; - } & BackwardsCompatibleSentryCarrier; /** * Raw module metadata that is injected by bundler plugins. * @@ -83,25 +48,7 @@ export type InternalGlobal = { */ _sentryModuleMetadata?: Record; _sentryEsmLoaderHookRegistered?: boolean; -}; +} & Carrier; /** Get's the global object for the current JavaScript runtime */ export const GLOBAL_OBJ = globalThis as unknown as InternalGlobal; - -/** - * Returns a global singleton contained in the global `__SENTRY__[]` object. - * - * If the singleton doesn't already exist in `__SENTRY__`, it will be created using the given factory - * function and added to the `__SENTRY__` object. - * - * @param name name of the global singleton on __SENTRY__ - * @param creator creator Factory function to create the singleton if it doesn't already exist on `__SENTRY__` - * @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `GLOBAL_OBJ`'s return value - * @returns the singleton - */ -export function getGlobalSingleton(name: keyof SentryCarrier, creator: () => T, obj?: unknown): T { - const gbl = (obj || GLOBAL_OBJ) as InternalGlobal; - const __SENTRY__ = (gbl.__SENTRY__ = gbl.__SENTRY__ || {}); - const versionedCarrier = (__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {}); - return versionedCarrier[name] || (versionedCarrier[name] = creator()); -} diff --git a/packages/core/test/lib/hint.test.ts b/packages/core/test/lib/hint.test.ts index d455a5bd5e44..f7fd5ff83ae4 100644 --- a/packages/core/test/lib/hint.test.ts +++ b/packages/core/test/lib/hint.test.ts @@ -14,7 +14,6 @@ describe('Hint', () => { afterEach(() => { jest.clearAllMocks(); - // @ts-expect-error for testing delete GLOBAL_OBJ.__SENTRY__; }); diff --git a/packages/core/test/utils-hoist/envelope.test.ts b/packages/core/test/utils-hoist/envelope.test.ts index ac1f17cfa1d9..6da22c1869a9 100644 --- a/packages/core/test/utils-hoist/envelope.test.ts +++ b/packages/core/test/utils-hoist/envelope.test.ts @@ -7,6 +7,7 @@ import { spanToJSON, } from '@sentry/core'; import { SentrySpan } from '@sentry/core'; +import { getSentryCarrier } from '../../src/carrier'; import { addItemToEnvelope, createEnvelope, @@ -107,17 +108,18 @@ describe('envelope', () => { { name: 'with TextEncoder/Decoder polyfill', before: () => { - GLOBAL_OBJ.__SENTRY__ = {} as InternalGlobal['__SENTRY__']; - GLOBAL_OBJ.__SENTRY__.encodePolyfill = jest.fn((input: string) => + GLOBAL_OBJ.__SENTRY__ = {}; + + getSentryCarrier(GLOBAL_OBJ).encodePolyfill = jest.fn((input: string) => new TextEncoder().encode(input), ); - GLOBAL_OBJ.__SENTRY__.decodePolyfill = jest.fn((input: Uint8Array) => + getSentryCarrier(GLOBAL_OBJ).decodePolyfill = jest.fn((input: Uint8Array) => new TextDecoder().decode(input), ); }, after: () => { - expect(GLOBAL_OBJ.__SENTRY__.encodePolyfill).toHaveBeenCalled(); - expect(GLOBAL_OBJ.__SENTRY__.decodePolyfill).toHaveBeenCalled(); + expect(getSentryCarrier(GLOBAL_OBJ).encodePolyfill).toHaveBeenCalled(); + expect(getSentryCarrier(GLOBAL_OBJ).decodePolyfill).toHaveBeenCalled(); }, }, {