From 4880618298a456ca2e20b9816e58afa197281fac Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 19 Apr 2022 12:46:27 -0400 Subject: [PATCH] feat(core): Introduce separate client options (#4927) This PR works toward differentiating the options that are passed into `Sentry.init` and the options passed into clients. We want to make this differentiation to minimize internal state in the client, and instead rely on the max number of items being passed in to the client constructor. We do this by explicitly differentiating between the options that are configured in sdk init (`Options`) and the options that are passed into the client constructor (`ClientOptions`). --- packages/browser/src/client.ts | 25 ++- packages/browser/src/sdk.ts | 26 ++- packages/browser/src/transports/setup.ts | 5 +- .../unit/helper/browser-client-options.ts | 12 ++ packages/browser/test/unit/index.test.ts | 111 +++++------ .../unit/integrations/linkederrors.test.ts | 7 +- packages/browser/test/unit/sdk.test.ts | 124 +++++++++++++ .../test/unit/transports/setup.test.ts | 9 +- packages/core/src/baseclient.ts | 4 +- packages/core/src/index.ts | 1 + packages/core/src/integration.ts | 6 +- packages/core/src/sdk.ts | 6 +- packages/core/test/lib/base.test.ts | 174 ++++++++---------- packages/core/test/lib/sdk.test.ts | 63 +------ packages/core/test/mocks/client.ts | 22 ++- packages/node/src/client.ts | 8 +- packages/node/src/sdk.ts | 22 ++- packages/node/src/types.ts | 23 ++- packages/node/test/client.test.ts | 27 +-- packages/node/test/handlers.test.ts | 23 +-- .../node/test/helper/node-client-options.ts | 12 ++ packages/node/test/index.test.ts | 39 ++-- packages/node/test/integrations/http.test.ts | 11 +- .../test/integrations/linkederrors.test.ts | 11 +- packages/node/test/sdk.test.ts | 92 +++++++++ packages/node/test/transports/setup.test.ts | 3 +- packages/tracing/src/hubextensions.ts | 11 +- packages/tracing/src/utils.ts | 4 +- .../test/browser/backgroundtab.test.ts | 3 +- .../test/browser/browsertracing.test.ts | 7 +- packages/tracing/test/browser/request.test.ts | 3 +- packages/tracing/test/errors.test.ts | 3 +- packages/tracing/test/hub.test.ts | 79 ++++---- packages/tracing/test/idletransaction.test.ts | 5 +- packages/tracing/test/span.test.ts | 11 +- packages/types/src/client.ts | 4 +- packages/types/src/index.ts | 2 +- packages/types/src/options.ts | 136 ++++++++------ packages/utils/src/dsn.ts | 2 - 39 files changed, 685 insertions(+), 451 deletions(-) create mode 100644 packages/browser/test/unit/helper/browser-client-options.ts create mode 100644 packages/browser/test/unit/sdk.test.ts create mode 100644 packages/node/test/helper/node-client-options.ts create mode 100644 packages/node/test/sdk.test.ts diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index 8666eaebe8d5..a889d7f69d06 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -1,5 +1,5 @@ import { BaseClient, NewTransport, Scope, SDK_VERSION } from '@sentry/core'; -import { Event, EventHint, Options, Severity, SeverityLevel, Transport } from '@sentry/types'; +import { ClientOptions, Event, EventHint, Options, Severity, SeverityLevel, Transport } from '@sentry/types'; import { getGlobalObject, logger, stackParserFromOptions } from '@sentry/utils'; import { eventFromException, eventFromMessage } from './eventbuilder'; @@ -7,11 +7,7 @@ import { IS_DEBUG_BUILD } from './flags'; import { injectReportDialog, ReportDialogOptions } from './helpers'; import { Breadcrumbs } from './integrations'; -/** - * Configuration options for the Sentry Browser SDK. - * @see BrowserClient for more information. - */ -export interface BrowserOptions extends Options { +export interface BaseBrowserOptions { /** * A pattern for error URLs which should exclusively be sent to Sentry. * This is the opposite of {@link Options.denyUrls}. @@ -27,19 +23,31 @@ export interface BrowserOptions extends Options { denyUrls?: Array; } +/** + * Configuration options for the Sentry Browser SDK. + * @see @sentry/types Options for more information. + */ +export interface BrowserOptions extends Options, BaseBrowserOptions {} + +/** + * Configuration options for the Sentry Browser SDK Client class + * @see BrowserClient for more information. + */ +export interface BrowserClientOptions extends ClientOptions, BaseBrowserOptions {} + /** * The Sentry Browser SDK Client. * * @see BrowserOptions for documentation on configuration options. * @see SentryClient for usage documentation. */ -export class BrowserClient extends BaseClient { +export class BrowserClient extends BaseClient { /** * Creates a new Browser SDK instance. * * @param options Configuration options for this SDK. */ - public constructor(options: BrowserOptions = {}, transport: Transport, newTransport?: NewTransport) { + public constructor(options: BrowserClientOptions, transport: Transport, newTransport?: NewTransport) { options._metadata = options._metadata || {}; options._metadata.sdk = options._metadata.sdk || { name: 'sentry.javascript.browser', @@ -51,7 +59,6 @@ export class BrowserClient extends BaseClient { ], version: SDK_VERSION, }; - super(options, transport, newTransport); } diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 94ec87cb4279..05d9d9c34462 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,12 +1,20 @@ -import { getCurrentHub, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; import { Hub } from '@sentry/types'; -import { addInstrumentationHandler, getGlobalObject, logger, resolvedSyncPromise } from '@sentry/utils'; +import { + addInstrumentationHandler, + getGlobalObject, + logger, + resolvedSyncPromise, + stackParserFromOptions, + supportsFetch, +} from '@sentry/utils'; -import { BrowserClient, BrowserOptions } from './client'; +import { BrowserClient, BrowserClientOptions, BrowserOptions } from './client'; import { IS_DEBUG_BUILD } from './flags'; import { ReportDialogOptions, wrap as internalWrap } from './helpers'; import { Breadcrumbs, Dedupe, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations'; import { defaultStackParsers } from './stack-parsers'; +import { FetchTransport, XHRTransport } from './transports'; import { setupBrowserTransport } from './transports/setup'; export const defaultIntegrations = [ @@ -97,9 +105,17 @@ export function init(options: BrowserOptions = {}): void { if (options.stackParser === undefined) { options.stackParser = defaultStackParsers; } - const { transport, newTransport } = setupBrowserTransport(options); - initAndBind(BrowserClient, options, transport, newTransport); + + const clientOptions: BrowserClientOptions = { + ...options, + stackParser: stackParserFromOptions(options), + integrations: getIntegrationsToSetup(options), + // TODO(v7): get rid of transport being passed down below + transport: options.transport || (supportsFetch() ? FetchTransport : XHRTransport), + }; + + initAndBind(BrowserClient, clientOptions, transport, newTransport); if (options.autoSessionTracking) { startSessionTracking(); diff --git a/packages/browser/src/transports/setup.ts b/packages/browser/src/transports/setup.ts index 0af6aad90676..f72365e7dc94 100644 --- a/packages/browser/src/transports/setup.ts +++ b/packages/browser/src/transports/setup.ts @@ -31,7 +31,10 @@ export interface BrowserTransportOptions extends BaseTransportOptions { * this function will return a ready to use `NewTransport`. */ // TODO(v7): Adjust return value when NewTransport is the default -export function setupBrowserTransport(options: BrowserOptions): { transport: Transport; newTransport?: NewTransport } { +export function setupBrowserTransport(options: BrowserOptions): { + transport: Transport; + newTransport?: NewTransport; +} { if (!options.dsn) { // We return the noop transport here in case there is no Dsn. return { transport: new NoopTransport() }; diff --git a/packages/browser/test/unit/helper/browser-client-options.ts b/packages/browser/test/unit/helper/browser-client-options.ts new file mode 100644 index 000000000000..aa763a5de06b --- /dev/null +++ b/packages/browser/test/unit/helper/browser-client-options.ts @@ -0,0 +1,12 @@ +import { NoopTransport } from '@sentry/core'; + +import { BrowserClientOptions } from '../../../src/client'; + +export function getDefaultBrowserClientOptions(options: Partial = {}): BrowserClientOptions { + return { + integrations: [], + transport: NoopTransport, + stackParser: () => [], + ...options, + }; +} diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index 9371e60073c1..434dea98977a 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -16,6 +16,7 @@ import { showReportDialog, wrap, } from '../../src'; +import { getDefaultBrowserClientOptions } from './helper/browser-client-options'; import { SimpleTransport } from './mocks/simpletransport'; const dsn = 'https://53039209a22b4ec1bcc296a3c9fdecd6@sentry.io/4291'; @@ -75,7 +76,8 @@ describe('SentryBrowser', () => { describe('showReportDialog', () => { describe('user', () => { const EX_USER = { email: 'test@example.com' }; - const client = new BrowserClient({ dsn }, new SimpleTransport({ dsn })); + const options = getDefaultBrowserClientOptions({ dsn }); + const client = new BrowserClient(options, new SimpleTransport({ dsn })); const reportDialogSpy = jest.spyOn(client, 'showReportDialog'); beforeEach(() => { @@ -139,53 +141,41 @@ describe('SentryBrowser', () => { }); it('should capture a message', done => { - getCurrentHub().bindClient( - new BrowserClient( - { - beforeSend: (event: Event): Event | null => { - expect(event.message).toBe('test'); - expect(event.exception).toBeUndefined(); - done(); - return event; - }, - dsn, - }, - new SimpleTransport({ dsn }), - ), - ); + const options = getDefaultBrowserClientOptions({ + beforeSend: (event: Event): Event | null => { + expect(event.message).toBe('test'); + expect(event.exception).toBeUndefined(); + done(); + return event; + }, + dsn, + }); + getCurrentHub().bindClient(new BrowserClient(options, new SimpleTransport({ dsn }))); captureMessage('test'); }); it('should capture an event', done => { - getCurrentHub().bindClient( - new BrowserClient( - { - beforeSend: (event: Event): Event | null => { - expect(event.message).toBe('event'); - expect(event.exception).toBeUndefined(); - done(); - return event; - }, - dsn, - }, - new SimpleTransport({ dsn }), - ), - ); + const options = getDefaultBrowserClientOptions({ + beforeSend: (event: Event): Event | null => { + expect(event.message).toBe('event'); + expect(event.exception).toBeUndefined(); + done(); + return event; + }, + dsn, + }); + getCurrentHub().bindClient(new BrowserClient(options, new SimpleTransport({ dsn }))); captureEvent({ message: 'event' }); }); it('should not dedupe an event on bound client', async () => { const localBeforeSend = jest.fn(); - getCurrentHub().bindClient( - new BrowserClient( - { - beforeSend: localBeforeSend, - dsn, - integrations: [], - }, - new SimpleTransport({ dsn }), - ), - ); + const options = getDefaultBrowserClientOptions({ + beforeSend: localBeforeSend, + dsn, + integrations: [], + }); + getCurrentHub().bindClient(new BrowserClient(options, new SimpleTransport({ dsn }))); captureMessage('event222'); captureMessage('event222'); @@ -197,16 +187,12 @@ describe('SentryBrowser', () => { it('should use inboundfilter rules of bound client', async () => { const localBeforeSend = jest.fn(); - getCurrentHub().bindClient( - new BrowserClient( - { - beforeSend: localBeforeSend, - dsn, - integrations: [new Integrations.InboundFilters({ ignoreErrors: ['capture'] })], - }, - new SimpleTransport({ dsn }), - ), - ); + const options = getDefaultBrowserClientOptions({ + beforeSend: localBeforeSend, + dsn, + integrations: [new Integrations.InboundFilters({ ignoreErrors: ['capture'] })], + }); + getCurrentHub().bindClient(new BrowserClient(options, new SimpleTransport({ dsn }))); captureMessage('capture'); @@ -267,7 +253,8 @@ describe('SentryBrowser initialization', () => { }); it('should set SDK data when instantiating a client directly', () => { - const client = new BrowserClient({ dsn }, new SimpleTransport({ dsn })); + const options = getDefaultBrowserClientOptions({ dsn }); + const client = new BrowserClient(options, new SimpleTransport({ dsn })); const sdkData = (client.getTransport() as any)._api.metadata?.sdk; @@ -309,20 +296,16 @@ describe('SentryBrowser initialization', () => { describe('wrap()', () => { it('should wrap and call function while capturing error', done => { - getCurrentHub().bindClient( - new BrowserClient( - { - beforeSend: (event: Event): Event | null => { - expect(event.exception!.values![0].type).toBe('TypeError'); - expect(event.exception!.values![0].value).toBe('mkey'); - done(); - return null; - }, - dsn, - }, - new SimpleTransport({ dsn }), - ), - ); + const options = getDefaultBrowserClientOptions({ + beforeSend: (event: Event): Event | null => { + expect(event.exception!.values![0].type).toBe('TypeError'); + expect(event.exception!.values![0].value).toBe('mkey'); + done(); + return null; + }, + dsn, + }); + getCurrentHub().bindClient(new BrowserClient(options, new SimpleTransport({ dsn }))); try { wrap(() => { diff --git a/packages/browser/test/unit/integrations/linkederrors.test.ts b/packages/browser/test/unit/integrations/linkederrors.test.ts index 6ccd4b5975a3..4b862705bccc 100644 --- a/packages/browser/test/unit/integrations/linkederrors.test.ts +++ b/packages/browser/test/unit/integrations/linkederrors.test.ts @@ -5,6 +5,7 @@ import { BrowserClient } from '../../../src/client'; import * as LinkedErrorsModule from '../../../src/integrations/linkederrors'; import { defaultStackParsers } from '../../../src/stack-parsers'; import { setupBrowserTransport } from '../../../src/transports'; +import { getDefaultBrowserClientOptions } from '../helper/browser-client-options'; const parser = createStackParser(...defaultStackParsers); @@ -45,7 +46,7 @@ describe('LinkedErrors', () => { one.cause = two; const originalException = one; - const options = { stackParser: parser }; + const options = getDefaultBrowserClientOptions({ stackParser: parser }); const client = new BrowserClient(options, setupBrowserTransport(options).transport); return client.eventFromException(originalException).then(event => { const result = LinkedErrorsModule._handler(parser, 'cause', 5, event, { @@ -76,7 +77,7 @@ describe('LinkedErrors', () => { one.reason = two; const originalException = one; - const options = { stackParser: parser }; + const options = getDefaultBrowserClientOptions({ stackParser: parser }); const client = new BrowserClient(options, setupBrowserTransport(options).transport); return client.eventFromException(originalException).then(event => { const result = LinkedErrorsModule._handler(parser, 'reason', 5, event, { @@ -103,7 +104,7 @@ describe('LinkedErrors', () => { one.cause = two; two.cause = three; - const options = { stackParser: parser }; + const options = getDefaultBrowserClientOptions({ stackParser: parser }); const client = new BrowserClient(options, setupBrowserTransport(options).transport); const originalException = one; return client.eventFromException(originalException).then(event => { diff --git a/packages/browser/test/unit/sdk.test.ts b/packages/browser/test/unit/sdk.test.ts new file mode 100644 index 000000000000..8814329f16e9 --- /dev/null +++ b/packages/browser/test/unit/sdk.test.ts @@ -0,0 +1,124 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import { NoopTransport, Scope } from '@sentry/core'; +import { MockIntegration } from '@sentry/core/test/lib/sdk.test'; +import { Client, Integration } from '@sentry/types'; + +import { BrowserOptions } from '../../src'; +import { init } from '../../src/sdk'; +// eslint-disable-next-line no-var +declare var global: any; + +const PUBLIC_DSN = 'https://username@domain/123'; + +function getDefaultBrowserOptions(options: Partial = {}): BrowserOptions { + return { + integrations: [], + transport: NoopTransport, + stackParser: () => [], + ...options, + }; +} + +jest.mock('@sentry/hub', () => { + const original = jest.requireActual('@sentry/hub'); + return { + ...original, + getCurrentHub(): { + bindClient(client: Client): boolean; + getClient(): boolean; + getScope(): Scope; + } { + return { + getClient(): boolean { + return false; + }, + getScope(): Scope { + return new Scope(); + }, + bindClient(client: Client): boolean { + client.setupIntegrations(); + return true; + }, + }; + }, + }; +}); + +describe('init', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + test('installs default integrations', () => { + const DEFAULT_INTEGRATIONS: Integration[] = [ + new MockIntegration('MockIntegration 0.1'), + new MockIntegration('MockIntegration 0.2'), + ]; + const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS }); + + init(options); + + expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + }); + + test("doesn't install default integrations if told not to", () => { + const DEFAULT_INTEGRATIONS: Integration[] = [ + new MockIntegration('MockIntegration 0.3'), + new MockIntegration('MockIntegration 0.4'), + ]; + const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: false }); + init(options); + + expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + }); + + it('installs merged default integrations, with overrides provided through options', () => { + const DEFAULT_INTEGRATIONS = [ + new MockIntegration('MockIntegration 1.1'), + new MockIntegration('MockIntegration 1.2'), + ]; + + const integrations = [new MockIntegration('MockIntegration 1.1'), new MockIntegration('MockIntegration 1.3')]; + const options = getDefaultBrowserOptions({ + dsn: PUBLIC_DSN, + defaultIntegrations: DEFAULT_INTEGRATIONS, + integrations, + }); + + init(options); + // 'MockIntegration 1' should be overridden by the one with the same name provided through options + expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(integrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(integrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + }); + + it('installs integrations returned from a callback function', () => { + const DEFAULT_INTEGRATIONS = [ + new MockIntegration('MockIntegration 2.1'), + new MockIntegration('MockIntegration 2.2'), + ]; + + const newIntegration = new MockIntegration('MockIntegration 2.3'); + const options = getDefaultBrowserOptions({ + defaultIntegrations: DEFAULT_INTEGRATIONS, + dsn: PUBLIC_DSN, + integrations: (integrations: Integration[]) => { + const t = integrations.slice(0, 1).concat(newIntegration); + return t; + }, + }); + + init(options); + + expect(DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + }); +}); diff --git a/packages/browser/test/unit/transports/setup.test.ts b/packages/browser/test/unit/transports/setup.test.ts index 2683a0619aea..41b361d684d5 100644 --- a/packages/browser/test/unit/transports/setup.test.ts +++ b/packages/browser/test/unit/transports/setup.test.ts @@ -7,6 +7,7 @@ import { setupBrowserTransport, XHRTransport, } from '../../../src/transports'; +import { getDefaultBrowserClientOptions } from '../helper/browser-client-options'; import { SimpleTransport } from '../mocks/simpletransport'; const DSN = 'https://username@domain/123'; @@ -64,7 +65,7 @@ describe('setupBrowserTransport', () => { }); it('returns the instantiated transport passed via the options', () => { - const options = { dsn: DSN, transport: SimpleTransport }; + const options = getDefaultBrowserClientOptions({ dsn: DSN, transport: SimpleTransport }); const { transport, newTransport } = setupBrowserTransport(options); expect(transport).toBeDefined(); @@ -73,7 +74,8 @@ describe('setupBrowserTransport', () => { }); it('returns fetchTransports if fetch is supported', () => { - const options = { dsn: DSN }; + const options = getDefaultBrowserClientOptions({ dsn: DSN }); + delete options.transport; const { transport, newTransport } = setupBrowserTransport(options); expect(transport).toBeDefined(); @@ -86,7 +88,8 @@ describe('setupBrowserTransport', () => { it('returns xhrTransports if fetch is not supported', () => { fetchSupported = false; - const options = { dsn: DSN }; + const options = getDefaultBrowserClientOptions({ dsn: DSN }); + delete options.transport; const { transport, newTransport } = setupBrowserTransport(options); expect(transport).toBeDefined(); diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 2888e9853f5e..3d82d829cead 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -2,12 +2,12 @@ import { Scope, Session } from '@sentry/hub'; import { Client, + ClientOptions, DsnComponents, Event, EventHint, Integration, IntegrationClass, - Options, Severity, SeverityLevel, Transport, @@ -68,7 +68,7 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca * // ... * } */ -export abstract class BaseClient implements Client { +export abstract class BaseClient implements Client { /** Options passed to the SDK. */ protected readonly _options: O; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ccf2aa519775..da68917c936e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -37,6 +37,7 @@ export { initAndBind } from './sdk'; export { NoopTransport } from './transports/noop'; export { createTransport } from './transports/base'; export { SDK_VERSION } from './version'; +export { getIntegrationsToSetup } from './integration'; import * as Integrations from './integrations'; diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index cc694c815289..b4f35e7d5b1e 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -1,5 +1,5 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/hub'; -import { Integration, Options } from '@sentry/types'; +import { ClientOptions, Integration, Options } from '@sentry/types'; import { addNonEnumerableProperty, logger } from '@sentry/utils'; import { IS_DEBUG_BUILD } from './flags'; @@ -70,9 +70,9 @@ export function setupIntegration(integration: Integration): void { * @param integrations array of integration instances * @param withDefault should enable default integrations */ -export function setupIntegrations(options: O): IntegrationIndex { +export function setupIntegrations(options: O): IntegrationIndex { const integrations: IntegrationIndex = {}; - getIntegrationsToSetup(options).forEach(integration => { + options.integrations.forEach(integration => { integrations[integration.name] = integration; setupIntegration(integration); }); diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index 97c8b349a235..c7f7bb4916a3 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -1,12 +1,12 @@ import { getCurrentHub } from '@sentry/hub'; -import { Client, Options, Transport } from '@sentry/types'; +import { Client, ClientOptions, Transport } from '@sentry/types'; import { logger } from '@sentry/utils'; import { IS_DEBUG_BUILD } from './flags'; import { NewTransport } from './transports/base'; /** A class object that can instantiate Client objects. */ -export type ClientClass = new ( +export type ClientClass = new ( options: O, transport: Transport, newTransport?: NewTransport, @@ -19,7 +19,7 @@ export type ClientClass = new ( * @param clientClass The client class to instantiate. * @param options Options to pass to the client. */ -export function initAndBind( +export function initAndBind( clientClass: ClientClass, options: O, transport: Transport, diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index 1bd2750e5378..47ec8ae70266 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -4,7 +4,7 @@ import { dsnToString, logger, SentryError, SyncPromise } from '@sentry/utils'; import * as integrationModule from '../../src/integration'; import { NoopTransport } from '../../src/transports/noop'; -import { setupTestTransport, TestClient } from '../mocks/client'; +import { getDefaultTestClientOptions, setupTestTransport, TestClient } from '../mocks/client'; import { TestIntegration } from '../mocks/integration'; import { FakeTransport } from '../mocks/transport'; @@ -67,7 +67,7 @@ describe('BaseClient', () => { test('returns the Dsn', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); expect(dsnToString(client.getDsn()!)).toBe(PUBLIC_DSN); }); @@ -75,7 +75,7 @@ describe('BaseClient', () => { test('allows missing Dsn', () => { expect.assertions(1); - const options = {}; + const options = getDefaultTestClientOptions(); const client = new TestClient(options, setupTestTransport(options).transport); expect(client.getDsn()).toBeUndefined(); @@ -84,7 +84,7 @@ describe('BaseClient', () => { test('throws with invalid Dsn', () => { expect.assertions(1); - const options = { dsn: 'abc' }; + const options = getDefaultTestClientOptions({ dsn: 'abc' }); expect(() => new TestClient(options, setupTestTransport(options).transport)).toThrow(SentryError); }); }); @@ -93,7 +93,7 @@ describe('BaseClient', () => { test('returns the options', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN, test: true }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, test: true }); const client = new TestClient(options, setupTestTransport(options).transport); expect(client.getOptions()).toEqual(options); @@ -104,7 +104,7 @@ describe('BaseClient', () => { test('returns the transport from client', () => { expect.assertions(2); - const options = { dsn: PUBLIC_DSN, transport: FakeTransport }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, transport: FakeTransport }); const client = new TestClient(options, new FakeTransport()); expect(client.getTransport()).toBeInstanceOf(FakeTransport); @@ -114,7 +114,7 @@ describe('BaseClient', () => { test('retruns NoopTransport when no transport is passed', () => { expect.assertions(2); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); expect(client.getTransport()).toBeInstanceOf(NoopTransport); @@ -126,7 +126,7 @@ describe('BaseClient', () => { test('adds a breadcrumb', () => { expect.assertions(1); - const options = {}; + const options = getDefaultTestClientOptions({}); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -140,7 +140,7 @@ describe('BaseClient', () => { test('adds a timestamp to new breadcrumbs', () => { expect.assertions(1); - const options = {}; + const options = getDefaultTestClientOptions({}); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -154,7 +154,7 @@ describe('BaseClient', () => { test('discards breadcrumbs beyond maxBreadcrumbs', () => { expect.assertions(2); - const options = { maxBreadcrumbs: 1 }; + const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -169,7 +169,7 @@ describe('BaseClient', () => { test('allows concurrent updates', () => { expect.assertions(1); - const options = {}; + const options = getDefaultTestClientOptions({}); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -184,7 +184,7 @@ describe('BaseClient', () => { expect.assertions(1); const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); - const options = { beforeBreadcrumb }; + const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -198,7 +198,7 @@ describe('BaseClient', () => { expect.assertions(1); const beforeBreadcrumb = jest.fn(() => ({ message: 'changed' })); - const options = { beforeBreadcrumb }; + const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -212,7 +212,7 @@ describe('BaseClient', () => { expect.assertions(1); const beforeBreadcrumb = jest.fn(() => null); - const options = { beforeBreadcrumb }; + const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -226,7 +226,7 @@ describe('BaseClient', () => { expect.assertions(2); const beforeBreadcrumb = jest.fn((breadcrumb, hint) => ({ ...breadcrumb, data: hint.data })); - const options = { beforeBreadcrumb }; + const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -240,7 +240,7 @@ describe('BaseClient', () => { describe('captureException', () => { test('captures and sends exceptions', () => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureException(new Error('test exception')); @@ -263,7 +263,7 @@ describe('BaseClient', () => { }); test('allows for providing explicit scope', () => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); scope.setExtra('foo', 'wat'); @@ -291,7 +291,7 @@ describe('BaseClient', () => { }); test('allows for clearing data from existing scope if explicit one does so in a callback function', () => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); scope.setExtra('foo', 'wat'); @@ -326,7 +326,7 @@ describe('BaseClient', () => { // already-seen check to work . Any primitive which is passed without being wrapped will be captured each time it // is encountered, so this test doesn't apply. ])("doesn't capture the same exception twice - %s", (_name: string, thrown: any) => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); expect(thrown.__sentry_captured__).toBeUndefined(); @@ -345,7 +345,7 @@ describe('BaseClient', () => { describe('captureMessage', () => { test('captures and sends messages', () => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureMessage('test message'); @@ -362,7 +362,7 @@ describe('BaseClient', () => { }); test('should call eventFromException if input to captureMessage is not a primitive', () => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const spy = jest.spyOn(TestClient.instance!, 'eventFromException'); @@ -381,7 +381,7 @@ describe('BaseClient', () => { }); test('allows for providing explicit scope', () => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); scope.setExtra('foo', 'wat'); @@ -415,7 +415,7 @@ describe('BaseClient', () => { test('skips when disabled', () => { expect.assertions(1); - const options = { enabled: false, dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ enabled: false, dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -427,7 +427,7 @@ describe('BaseClient', () => { test('skips without a Dsn', () => { expect.assertions(1); - const options = {}; + const options = getDefaultTestClientOptions({}); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -445,7 +445,7 @@ describe('BaseClient', () => { // already-seen check to work . Any primitive which is passed without being wrapped will be captured each time it // is encountered, so this test doesn't apply. ])("doesn't capture an event wrapping the same exception twice - %s", (_name: string, thrown: any) => { - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); // Note: this is the same test as in `describe(captureException)`, except with the exception already wrapped in a // hint and accompanying an event. Duplicated here because some methods skip `captureException` and go straight to // `captureEvent`. @@ -469,7 +469,7 @@ describe('BaseClient', () => { test('sends an event', () => { expect.assertions(2); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -489,7 +489,7 @@ describe('BaseClient', () => { test('does not overwrite existing timestamp', () => { expect.assertions(2); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -509,7 +509,7 @@ describe('BaseClient', () => { test('adds event_id from hint if available', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -528,9 +528,7 @@ describe('BaseClient', () => { test('sets default environment to `production` if none provided', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -549,10 +547,7 @@ describe('BaseClient', () => { test('adds the configured environment', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - environment: 'env', - }; + const options = getDefaultTestClientOptions({ environment: 'env', dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -571,10 +566,7 @@ describe('BaseClient', () => { test('allows for environment to be explicitly set to falsy value', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - environment: undefined, - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, environment: undefined }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -593,10 +585,7 @@ describe('BaseClient', () => { test('adds the configured release', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - release: 'v1.0.0', - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, release: 'v1.0.0' }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); @@ -616,7 +605,7 @@ describe('BaseClient', () => { test('adds breadcrumbs', () => { expect.assertions(4); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); scope.addBreadcrumb({ message: 'breadcrumb' }, 100); @@ -632,7 +621,7 @@ describe('BaseClient', () => { test('limits previously saved breadcrumbs', () => { expect.assertions(2); - const options = { dsn: PUBLIC_DSN, maxBreadcrumbs: 1 }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, maxBreadcrumbs: 1 }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); const hub = new Hub(client, scope); @@ -648,7 +637,7 @@ describe('BaseClient', () => { test('adds context data', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); scope.setExtra('b', 'b'); @@ -673,7 +662,7 @@ describe('BaseClient', () => { test('adds fingerprint', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const scope = new Scope(); scope.setFingerprint(['abcd']); @@ -692,7 +681,7 @@ describe('BaseClient', () => { }); test('adds installed integrations to sdk info', () => { - const options = { dsn: PUBLIC_DSN, integrations: [new TestIntegration()] }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations: [new TestIntegration()] }); const client = new TestClient(options, setupTestTransport(options).transport); client.setupIntegrations(); @@ -706,7 +695,7 @@ describe('BaseClient', () => { test('normalizes event with default depth of 3', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const fourLevelsObject = { a: { @@ -758,10 +747,7 @@ describe('BaseClient', () => { test('normalization respects `normalizeDepth` option', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - normalizeDepth: 2, - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, normalizeDepth: 2 }); const client = new TestClient(options, setupTestTransport(options).transport); const fourLevelsObject = { a: { @@ -810,10 +796,7 @@ describe('BaseClient', () => { test('skips normalization when `normalizeDepth: 0`', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - normalizeDepth: 0, - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, normalizeDepth: 0 }); const client = new TestClient(options, setupTestTransport(options).transport); const fourLevelsObject = { a: { @@ -867,7 +850,7 @@ describe('BaseClient', () => { test('normalization applies to Transaction and Span consistently', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const transaction: Event = { contexts: { @@ -942,7 +925,7 @@ describe('BaseClient', () => { expect.assertions(1); const beforeSend = jest.fn(event => event); - const options = { dsn: PUBLIC_DSN, beforeSend }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureEvent({ message: 'hello' }); @@ -954,7 +937,7 @@ describe('BaseClient', () => { expect.assertions(1); const beforeSend = jest.fn(() => ({ message: 'changed1' })); - const options = { dsn: PUBLIC_DSN, beforeSend }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureEvent({ message: 'hello' }); @@ -966,7 +949,8 @@ describe('BaseClient', () => { expect.assertions(3); const beforeSend = jest.fn(() => null); - const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }, setupTestTransport({ dsn: PUBLIC_DSN }).transport); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); + const client = new TestClient(options, setupTestTransport(options).transport); const captureExceptionSpy = jest.spyOn(client, 'captureException'); const loggerErrorSpy = jest.spyOn(logger, 'error'); @@ -983,9 +967,9 @@ describe('BaseClient', () => { for (const val of invalidValues) { const beforeSend = jest.fn(() => val); - const options = { dsn: PUBLIC_DSN, beforeSend }; // @ts-ignore we need to test regular-js behavior - const client = new TestClient(options, setupTestTransport({ dsn: PUBLIC_DSN }).transport); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); + const client = new TestClient(options, setupTestTransport(options).transport); const loggerErrorSpy = jest.spyOn(logger, 'error'); client.captureEvent({ message: 'hello' }); @@ -1009,7 +993,7 @@ describe('BaseClient', () => { }, 1); }), ); - const options = { dsn: PUBLIC_DSN, beforeSend }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureEvent({ message: 'hello' }); @@ -1038,7 +1022,7 @@ describe('BaseClient', () => { }, 1); }), ); - const options = { dsn: PUBLIC_DSN, beforeSend }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureEvent({ message: 'hello' }); @@ -1067,7 +1051,7 @@ describe('BaseClient', () => { }); }), ); - const options = { dsn: PUBLIC_DSN, beforeSend }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureEvent({ message: 'hello' }); @@ -1080,7 +1064,7 @@ describe('BaseClient', () => { expect.assertions(2); const beforeSend = jest.fn((event, hint) => ({ ...event, data: hint.data })); - const options = { dsn: PUBLIC_DSN, beforeSend }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options, setupTestTransport(options).transport); client.captureEvent({ message: 'hello' }, { data: 'someRandomThing' }); @@ -1093,13 +1077,13 @@ describe('BaseClient', () => { expect.assertions(1); const client = new TestClient( - { + getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend() { return null; }, - }, - setupTestTransport({ dsn: PUBLIC_DSN }).transport, + }), + setupTestTransport(getDefaultTestClientOptions({ dsn: PUBLIC_DSN })).transport, ); const recordLostEventSpy = jest.fn(); jest.spyOn(client, 'getTransport').mockImplementationOnce( @@ -1117,7 +1101,10 @@ describe('BaseClient', () => { test('eventProcessor can drop the even when it returns null', () => { expect.assertions(3); - const client = new TestClient({ dsn: PUBLIC_DSN }, setupTestTransport({ dsn: PUBLIC_DSN }).transport); + const client = new TestClient( + getDefaultTestClientOptions({ dsn: PUBLIC_DSN }), + setupTestTransport(getDefaultTestClientOptions({ dsn: PUBLIC_DSN })).transport, + ); const captureExceptionSpy = jest.spyOn(client, 'captureException'); const loggerErrorSpy = jest.spyOn(logger, 'error'); const scope = new Scope(); @@ -1133,7 +1120,7 @@ describe('BaseClient', () => { test('eventProcessor records dropped events', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const recordLostEventSpy = jest.fn(); @@ -1155,7 +1142,7 @@ describe('BaseClient', () => { test('eventProcessor sends an event and logs when it crashes', () => { expect.assertions(3); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const captureExceptionSpy = jest.spyOn(client, 'captureException'); const loggerErrorSpy = jest.spyOn(logger, 'error'); @@ -1184,10 +1171,7 @@ describe('BaseClient', () => { test('records events dropped due to sampleRate', () => { expect.assertions(1); - const options = { - dsn: PUBLIC_DSN, - sampleRate: 0, - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, sampleRate: 0 }); const client = new TestClient(options, setupTestTransport(options).transport); const recordLostEventSpy = jest.fn(); @@ -1211,10 +1195,7 @@ describe('BaseClient', () => { test('setup each one of them on setupIntegration call', () => { expect.assertions(2); - const options = { - dsn: PUBLIC_DSN, - integrations: [new TestIntegration()], - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations: [new TestIntegration()] }); const client = new TestClient(options, setupTestTransport(options).transport); client.setupIntegrations(); @@ -1225,9 +1206,7 @@ describe('BaseClient', () => { test('skips installation if DSN is not provided', () => { expect.assertions(2); - const options = { - integrations: [new TestIntegration()], - }; + const options = getDefaultTestClientOptions({ integrations: [new TestIntegration()] }); const client = new TestClient(options, setupTestTransport(options).transport); client.setupIntegrations(); @@ -1238,11 +1217,11 @@ describe('BaseClient', () => { test('skips installation if enabled is set to false', () => { expect.assertions(2); - const options = { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enabled: false, integrations: [new TestIntegration()], - }; + }); const client = new TestClient(options, setupTestTransport(options).transport); client.setupIntegrations(); @@ -1253,10 +1232,7 @@ describe('BaseClient', () => { test('skips installation if integrations are already installed', () => { expect.assertions(4); - const options = { - dsn: PUBLIC_DSN, - integrations: [new TestIntegration()], - }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations: [new TestIntegration()] }); const client = new TestClient(options, setupTestTransport(options).transport); // note: not the `Client` method `setupIntegrations`, but the free-standing function which that method calls const setupIntegrationsHelper = jest.spyOn(integrationModule, 'setupIntegrations'); @@ -1281,11 +1257,11 @@ describe('BaseClient', () => { expect.assertions(5); const client = new TestClient( - { + getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableSend: true, transport: FakeTransport, - }, + }), new FakeTransport(), ); @@ -1310,11 +1286,11 @@ describe('BaseClient', () => { expect.assertions(5); const client = new TestClient( - { + getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableSend: true, transport: FakeTransport, - }, + }), new FakeTransport(), ); @@ -1349,11 +1325,11 @@ describe('BaseClient', () => { expect.assertions(2); const client = new TestClient( - { + getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableSend: true, transport: FakeTransport, - }, + }), new FakeTransport(), ); @@ -1373,7 +1349,7 @@ describe('BaseClient', () => { jest.useRealTimers(); expect.assertions(3); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); return Promise.all([ @@ -1394,7 +1370,7 @@ describe('BaseClient', () => { test('sends sessions to the client', () => { expect.assertions(1); - const options = { dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const session = new Session({ release: 'test' }); @@ -1406,7 +1382,7 @@ describe('BaseClient', () => { test('skips when disabled', () => { expect.assertions(1); - const options = { enabled: false, dsn: PUBLIC_DSN }; + const options = getDefaultTestClientOptions({ enabled: false, dsn: PUBLIC_DSN }); const client = new TestClient(options, setupTestTransport(options).transport); const session = new Session({ release: 'test' }); diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index 578361a97511..7dd3229c5c7e 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -3,7 +3,7 @@ import { Client, Integration } from '@sentry/types'; import { installedIntegrations } from '../../src/integration'; import { initAndBind } from '../../src/sdk'; -import { setupTestTransport, TestClient, TestOptions } from '../mocks/client'; +import { getDefaultTestClientOptions, setupTestTransport, TestClient } from '../mocks/client'; // eslint-disable-next-line no-var declare var global: any; @@ -35,7 +35,7 @@ jest.mock('@sentry/hub', () => { }; }); -class MockIntegration implements Integration { +export class MockIntegration implements Integration { public name: string; public setupOnce: () => void = jest.fn(); public constructor(name: string) { @@ -50,72 +50,15 @@ describe('SDK', () => { }); describe('initAndBind', () => { - test('installs default integrations', () => { - const DEFAULT_INTEGRATIONS: Integration[] = [ - new MockIntegration('MockIntegration 1'), - new MockIntegration('MockIntegration 2'), - ]; - const options = { dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS }; - initAndBind(TestClient, options, setupTestTransport(options).transport); - expect((DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).mock.calls.length).toBe(1); - expect((DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).mock.calls.length).toBe(1); - }); - - test("doesn't install default integrations if told not to", () => { - const DEFAULT_INTEGRATIONS: Integration[] = [ - new MockIntegration('MockIntegration 1'), - new MockIntegration('MockIntegration 2'), - ]; - const options: TestOptions = { dsn: PUBLIC_DSN, defaultIntegrations: false }; - initAndBind(TestClient, options, setupTestTransport(options).transport); - expect((DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).mock.calls.length).toBe(0); - expect((DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).mock.calls.length).toBe(0); - }); - test('installs integrations provided through options', () => { const integrations: Integration[] = [ new MockIntegration('MockIntegration 1'), new MockIntegration('MockIntegration 2'), ]; - const options = { dsn: PUBLIC_DSN, integrations }; - initAndBind(TestClient, options, setupTestTransport(options).transport); - expect((integrations[0].setupOnce as jest.Mock).mock.calls.length).toBe(1); - expect((integrations[1].setupOnce as jest.Mock).mock.calls.length).toBe(1); - }); - - test('installs merged default integrations, with overrides provided through options', () => { - const DEFAULT_INTEGRATIONS: Integration[] = [ - new MockIntegration('MockIntegration 1'), - new MockIntegration('MockIntegration 2'), - ]; - const integrations: Integration[] = [ - new MockIntegration('MockIntegration 1'), - new MockIntegration('MockIntegration 3'), - ]; - const options = { dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS, integrations }; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations }); initAndBind(TestClient, options, setupTestTransport(options).transport); - // 'MockIntegration 1' should be overridden by the one with the same name provided through options - expect((DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).mock.calls.length).toBe(0); - expect((DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).mock.calls.length).toBe(1); expect((integrations[0].setupOnce as jest.Mock).mock.calls.length).toBe(1); expect((integrations[1].setupOnce as jest.Mock).mock.calls.length).toBe(1); }); - - test('installs integrations returned from a callback function', () => { - const DEFAULT_INTEGRATIONS: Integration[] = [ - new MockIntegration('MockIntegration 1'), - new MockIntegration('MockIntegration 2'), - ]; - const newIntegration = new MockIntegration('MockIntegration 3'); - const options = { - defaultIntegrations: DEFAULT_INTEGRATIONS, - dsn: PUBLIC_DSN, - integrations: (integrations: Integration[]) => integrations.slice(0, 1).concat(newIntegration), - }; - initAndBind(TestClient, options, setupTestTransport(options).transport); - expect((DEFAULT_INTEGRATIONS[0].setupOnce as jest.Mock).mock.calls.length).toBe(1); - expect((newIntegration.setupOnce as jest.Mock).mock.calls.length).toBe(1); - expect((DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).mock.calls.length).toBe(0); - }); }); }); diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 707513ff91b1..5778dcf5e193 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -1,26 +1,36 @@ import { Session } from '@sentry/hub'; -import { Event, Integration, Options, Severity, SeverityLevel, Transport } from '@sentry/types'; +import { ClientOptions, Event, Integration, Severity, SeverityLevel, Transport } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; import { BaseClient } from '../../src/baseclient'; import { initAndBind } from '../../src/sdk'; import { NewTransport } from '../../src/transports/base'; import { NoopTransport } from '../../src/transports/noop'; -export interface TestOptions extends Options { + +export function getDefaultTestClientOptions(options: Partial = {}): TestClientOptions { + return { + integrations: [], + transport: NoopTransport, + stackParser: () => [], + ...options, + }; +} + +export interface TestClientOptions extends ClientOptions { test?: boolean; mockInstallFailure?: boolean; enableSend?: boolean; defaultIntegrations?: Integration[] | false; } -export class TestClient extends BaseClient { +export class TestClient extends BaseClient { public static instance?: TestClient; public static sendEventCalled?: (event: Event) => void; public event?: Event; public session?: Session; - public constructor(options: TestOptions, transport: Transport, newTransport?: NewTransport) { + public constructor(options: TestClientOptions, transport: Transport, newTransport?: NewTransport) { super(options, transport, newTransport); TestClient.instance = this; } @@ -64,11 +74,11 @@ export class TestClient extends BaseClient { } } -export function init(options: TestOptions, transport: Transport, newTransport?: NewTransport): void { +export function init(options: TestClientOptions, transport: Transport, newTransport?: NewTransport): void { initAndBind(TestClient, options, transport, newTransport); } -export function setupTestTransport(options: TestOptions): { transport: Transport; newTransport?: NewTransport } { +export function setupTestTransport(options: TestClientOptions): { transport: Transport; newTransport?: NewTransport } { const noop = { transport: new NoopTransport() }; if (!options.dsn) { diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index e660c6c7b421..7864fd1b7ccc 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -5,22 +5,22 @@ import { logger, resolvedSyncPromise, stackParserFromOptions } from '@sentry/uti import { eventFromMessage, eventFromUnknownInput } from './eventbuilder'; import { IS_DEBUG_BUILD } from './flags'; -import { NodeOptions } from './types'; +import { NodeClientOptions } from './types'; /** * The Sentry Node SDK Client. * - * @see NodeOptions for documentation on configuration options. + * @see NodeClientOptions for documentation on configuration options. * @see SentryClient for usage documentation. */ -export class NodeClient extends BaseClient { +export class NodeClient extends BaseClient { protected _sessionFlusher: SessionFlusher | undefined; /** * Creates a new Node SDK instance. * @param options Configuration options for this SDK. */ - public constructor(options: NodeOptions, transport: Transport, newTransport?: NewTransport) { + public constructor(options: NodeClientOptions, transport: Transport, newTransport?: NewTransport) { options._metadata = options._metadata || {}; options._metadata.sdk = options._metadata.sdk || { name: 'sentry.javascript.node', diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 0b7fb9bd5671..fa4b22a12066 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -1,15 +1,15 @@ -import { getCurrentHub, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; import { getMainCarrier, setHubOnCarrier } from '@sentry/hub'; import { SessionStatus } from '@sentry/types'; -import { getGlobalObject, logger } from '@sentry/utils'; +import { getGlobalObject, logger, stackParserFromOptions } from '@sentry/utils'; import * as domain from 'domain'; import { NodeClient } from './client'; import { IS_DEBUG_BUILD } from './flags'; import { Console, ContextLines, Http, LinkedErrors, OnUncaughtException, OnUnhandledRejection } from './integrations'; import { nodeStackParser } from './stack-parser'; -import { setupNodeTransport } from './transports'; -import { NodeOptions } from './types'; +import { HTTPSTransport, HTTPTransport, setupNodeTransport } from './transports'; +import { NodeClientOptions, NodeOptions } from './types'; export const defaultIntegrations = [ // Common @@ -132,7 +132,17 @@ export function init(options: NodeOptions = {}): void { } const { transport, newTransport } = setupNodeTransport(options); - initAndBind(NodeClient, options, transport, newTransport); + + // TODO(v7): Refactor this to reduce the logic above + const clientOptions: NodeClientOptions = { + ...options, + stackParser: stackParserFromOptions(options), + integrations: getIntegrationsToSetup(options), + // TODO(v7): Fix me when we switch to new transports entirely. + transport: options.transport || (transport instanceof HTTPTransport ? HTTPTransport : HTTPSTransport), + }; + + initAndBind(NodeClient, clientOptions, transport, newTransport); if (options.autoSessionTracking) { startSessionTracking(); @@ -189,7 +199,7 @@ export function isAutoSessionTrackingEnabled(client?: NodeClient): boolean { if (client === undefined) { return false; } - const clientOptions: NodeOptions = client && client.getOptions(); + const clientOptions = client && client.getOptions(); if (clientOptions && clientOptions.autoSessionTracking !== undefined) { return clientOptions.autoSessionTracking; } diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index 055006a47e9e..a2311a4698f9 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -1,16 +1,9 @@ -import { Options } from '@sentry/types'; +import { ClientOptions, Options } from '@sentry/types'; -/** - * Configuration options for the Sentry Node SDK. - * @see NodeClient for more information. - */ -export interface NodeOptions extends Options { +export interface BaseNodeOptions { /** Sets an optional server name (device name) */ serverName?: string; - /** Maximum time in milliseconds to wait to drain the request queue, before the process is allowed to exit. */ - shutdownTimeout?: number; - /** Set a HTTP proxy that should be used for outbound requests. */ httpProxy?: string; @@ -23,3 +16,15 @@ export interface NodeOptions extends Options { /** Callback that is executed when a fatal global error occurs. */ onFatalError?(error: Error): void; } + +/** + * Configuration options for the Sentry Node SDK + * @see @sentry/types Options for more information. + */ +export interface NodeOptions extends Options, BaseNodeOptions {} + +/** + * Configuration options for the Sentry Node SDK Client class + * @see NodeClient for more information. + */ +export interface NodeClientOptions extends ClientOptions, BaseNodeOptions {} diff --git a/packages/node/test/client.test.ts b/packages/node/test/client.test.ts index 1cddd0e85e3f..bb3d13ca4122 100644 --- a/packages/node/test/client.test.ts +++ b/packages/node/test/client.test.ts @@ -2,6 +2,7 @@ import { Scope, SessionFlusher } from '@sentry/hub'; import { NodeClient } from '../src'; import { setupNodeTransport } from '../src/transports'; +import { getDefaultNodeClientOptions } from './helper/node-client-options'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -15,7 +16,7 @@ describe('NodeClient', () => { describe('captureException', () => { test('when autoSessionTracking is enabled, and requestHandler is not used -> requestStatus should not be set', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }); client = new NodeClient(options, setupNodeTransport(options).transport); const scope = new Scope(); scope.setRequestSession({ status: 'ok' }); @@ -26,7 +27,7 @@ describe('NodeClient', () => { expect(requestSession!.status).toEqual('ok'); }); test('when autoSessionTracking is disabled -> requestStatus should not be set', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: false, release: '1.4' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: false, release: '1.4' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -41,7 +42,7 @@ describe('NodeClient', () => { expect(requestSession!.status).toEqual('ok'); }); test('when autoSessionTracking is enabled + requestSession status is Crashed -> requestStatus should not be overridden', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -56,7 +57,7 @@ describe('NodeClient', () => { expect(requestSession!.status).toEqual('crashed'); }); test('when autoSessionTracking is enabled + error occurs within request bounds -> requestStatus should be set to Errored', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -71,7 +72,7 @@ describe('NodeClient', () => { expect(requestSession!.status).toEqual('errored'); }); test('when autoSessionTracking is enabled + error occurs outside of request bounds -> requestStatus should not be set to Errored', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -88,7 +89,7 @@ describe('NodeClient', () => { describe('captureEvent()', () => { test('If autoSessionTracking is disabled, requestSession status should not be set', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: false, release: '1.4' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: false, release: '1.4' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -107,7 +108,7 @@ describe('NodeClient', () => { }); test('When captureEvent is called with an exception, requestSession status should be set to Errored', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '2.2' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '2.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -123,7 +124,7 @@ describe('NodeClient', () => { }); test('When captureEvent is called without an exception, requestSession status should not be set to Errored', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '2.2' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '2.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -139,7 +140,7 @@ describe('NodeClient', () => { }); test('When captureEvent is called with an exception but outside of a request, then requestStatus should not be set', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '2.2' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '2.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -157,7 +158,7 @@ describe('NodeClient', () => { }); test('When captureEvent is called with a transaction, then requestSession status should not be set', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.3' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.3' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -172,7 +173,7 @@ describe('NodeClient', () => { }); test('When captureEvent is called with an exception but requestHandler is not used, then requestSession status should not be set', () => { - const options = { dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.3' }; + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.3' }); client = new NodeClient(options, setupNodeTransport(options).transport); const scope = new Scope(); @@ -192,11 +193,11 @@ describe('NodeClient', () => { describe('flush/close', () => { test('client close function disables _sessionFlusher', async () => { jest.useRealTimers(); - const options = { + const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.1', - }; + }); const client = new NodeClient(options, setupNodeTransport(options).transport); client.initSessionFlusher(); // Clearing interval is important here to ensure that the flush function later on is called by the `client.close()` diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index d7d47fda053d..b857e1b74bf5 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -1,6 +1,6 @@ import * as sentryCore from '@sentry/core'; -import { Hub } from '@sentry/hub'; import * as sentryHub from '@sentry/hub'; +import { Hub } from '@sentry/hub'; import { Transaction } from '@sentry/tracing'; import { Runtime } from '@sentry/types'; import { SentryError } from '@sentry/utils'; @@ -19,6 +19,7 @@ import { } from '../src/handlers'; import * as SDK from '../src/sdk'; import { setupNodeTransport } from '../src/transports'; +import { getDefaultNodeClientOptions } from './helper/node-client-options'; describe('parseRequest', () => { let mockReq: { [key: string]: any }; @@ -224,7 +225,7 @@ describe('requestHandler', () => { }); it('autoSessionTracking is enabled, sets requestSession status to ok, when handling a request', () => { - const options = { autoSessionTracking: true, release: '1.2' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); const hub = new Hub(client); @@ -237,7 +238,7 @@ describe('requestHandler', () => { }); it('autoSessionTracking is disabled, does not set requestSession, when handling a request', () => { - const options = { autoSessionTracking: false, release: '1.2' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); const hub = new Hub(client); @@ -250,7 +251,7 @@ describe('requestHandler', () => { }); it('autoSessionTracking is enabled, calls _captureRequestSession, on response finish', done => { - const options = { autoSessionTracking: true, release: '1.2' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); const hub = new Hub(client); @@ -271,7 +272,7 @@ describe('requestHandler', () => { }); it('autoSessionTracking is disabled, does not call _captureRequestSession, on response finish', done => { - const options = { autoSessionTracking: false, release: '1.2' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); const hub = new Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -372,7 +373,7 @@ describe('tracingHandler', () => { it('extracts request data for sampling context', () => { const tracesSampler = jest.fn(); - const options = { tracesSampler }; + const options = getDefaultNodeClientOptions({ tracesSampler }); const hub = new Hub(new NodeClient(options, setupNodeTransport(options).transport)); // we need to mock both of these because the tracing handler relies on `@sentry/core` while the sampler relies on // `@sentry/hub`, and mocking breaks the link between the two @@ -395,7 +396,7 @@ describe('tracingHandler', () => { }); it('puts its transaction on the scope', () => { - const options = { tracesSampleRate: 1.0 }; + const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); const hub = new Hub(new NodeClient(options, setupNodeTransport(options).transport)); // we need to mock both of these because the tracing handler relies on `@sentry/core` while the sampler relies on // `@sentry/hub`, and mocking breaks the link between the two @@ -727,7 +728,7 @@ describe('errorHandler()', () => { jest.restoreAllMocks(); }); it('when autoSessionTracking is disabled, does not set requestSession status on Crash', () => { - const options = { autoSessionTracking: false, release: '3.3' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -747,7 +748,7 @@ describe('errorHandler()', () => { }); it('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', () => { - const options = { autoSessionTracking: false, release: '3.3' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); client = new NodeClient(options, setupNodeTransport(options).transport); const scope = sentryCore.getCurrentHub().getScope(); @@ -764,7 +765,7 @@ describe('errorHandler()', () => { }); it('when autoSessionTracking is enabled, should set requestSession status to Crashed when an unhandled error occurs within the bounds of a request', () => { - const options = { autoSessionTracking: true, release: '1.1' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.1' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) @@ -783,7 +784,7 @@ describe('errorHandler()', () => { }); it('when autoSessionTracking is enabled, should not set requestSession status on Crash when it occurs outside the bounds of a request', () => { - const options = { autoSessionTracking: true, release: '2.2' }; + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); client = new NodeClient(options, setupNodeTransport(options).transport); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) diff --git a/packages/node/test/helper/node-client-options.ts b/packages/node/test/helper/node-client-options.ts new file mode 100644 index 000000000000..c2bb1d42a871 --- /dev/null +++ b/packages/node/test/helper/node-client-options.ts @@ -0,0 +1,12 @@ +import { NoopTransport } from '@sentry/core'; + +import { NodeClientOptions } from '../../src/types'; + +export function getDefaultNodeClientOptions(options: Partial = {}): NodeClientOptions { + return { + integrations: [], + transport: NoopTransport, + stackParser: () => [], + ...options, + }; +} diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index 02eaad72b2c0..2bdf8497097c 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -19,6 +19,7 @@ import { import { ContextLines, LinkedErrors } from '../src/integrations'; import { nodeStackParser } from '../src/stack-parser'; import { setupNodeTransport } from '../src/transports'; +import { getDefaultNodeClientOptions } from './helper/node-client-options'; const stackParser = createStackParser(nodeStackParser); @@ -90,7 +91,7 @@ describe('SentryNode', () => { }); test('record auto breadcrumbs', done => { - const options = { + const options = getDefaultNodeClientOptions({ beforeSend: (event: Event) => { // TODO: It should be 3, but we don't capture a breadcrumb // for our own captureMessage/captureException calls yet @@ -100,7 +101,7 @@ describe('SentryNode', () => { }, dsn, stackParser, - }; + }); const client = new NodeClient(options, setupNodeTransport(options).transport); getCurrentHub().bindClient(client); addBreadcrumb({ message: 'test1' }); @@ -122,7 +123,7 @@ describe('SentryNode', () => { test('capture an exception', done => { expect.assertions(6); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect(event.tags).toEqual({ test: '1' }); @@ -135,7 +136,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); configureScope((scope: Scope) => { scope.setTag('test', '1'); @@ -149,7 +150,7 @@ describe('SentryNode', () => { test('capture a string exception', done => { expect.assertions(6); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect(event.tags).toEqual({ test: '1' }); @@ -162,7 +163,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); configureScope((scope: Scope) => { scope.setTag('test', '1'); @@ -176,7 +177,7 @@ describe('SentryNode', () => { test('capture an exception with pre/post context', done => { expect.assertions(10); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect(event.tags).toEqual({ test: '1' }); @@ -193,7 +194,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); configureScope((scope: Scope) => { scope.setTag('test', '1'); @@ -207,7 +208,7 @@ describe('SentryNode', () => { test('capture a linked exception with pre/post context', done => { expect.assertions(15); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, integrations: [new ContextLines(), new LinkedErrors()], beforeSend: (event: Event) => { @@ -231,7 +232,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); try { throw new Error('test'); @@ -247,7 +248,7 @@ describe('SentryNode', () => { test('capture a message', done => { expect.assertions(2); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect(event.message).toBe('test'); @@ -256,14 +257,14 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); captureMessage('test'); }); test('capture an event', done => { expect.assertions(2); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect(event.message).toBe('test event'); @@ -272,7 +273,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); captureEvent({ message: 'test event' }); }); @@ -280,7 +281,7 @@ describe('SentryNode', () => { test('capture an event in a domain', done => { const d = domain.create(); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect(event.message).toBe('test domain'); @@ -289,7 +290,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); const client = new NodeClient(options, setupNodeTransport(options).transport); d.run(() => { @@ -301,7 +302,7 @@ describe('SentryNode', () => { test('stacktrace order', done => { expect.assertions(1); - const options = { + const options = getDefaultNodeClientOptions({ stackParser, beforeSend: (event: Event) => { expect( @@ -312,7 +313,7 @@ describe('SentryNode', () => { return null; }, dsn, - }; + }); getCurrentHub().bindClient(new NodeClient(options, setupNodeTransport(options).transport)); try { // @ts-ignore allow function declarations in strict mode @@ -376,7 +377,7 @@ describe('SentryNode initialization', () => { }); it('should set SDK data when instantiating a client directly', () => { - const options = { dsn }; + const options = getDefaultNodeClientOptions({ dsn }); const client = new NodeClient(options, setupNodeTransport(options).transport); // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index f66f847cd298..30a923524a3e 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,6 +1,6 @@ import * as sentryCore from '@sentry/core'; -import { Hub } from '@sentry/hub'; import * as hubModule from '@sentry/hub'; +import { Hub } from '@sentry/hub'; import { addExtensionMethods, Span, TRACEPARENT_REGEXP, Transaction } from '@sentry/tracing'; import { parseSemver } from '@sentry/utils'; import * as http from 'http'; @@ -12,16 +12,17 @@ import { Breadcrumb } from '../../src'; import { NodeClient } from '../../src/client'; import { Http as HttpIntegration } from '../../src/integrations/http'; import { setupNodeTransport } from '../../src/transports'; +import { getDefaultNodeClientOptions } from '../helper/node-client-options'; const NODE_VERSION = parseSemver(process.versions.node); describe('tracing', () => { function createTransactionOnScope() { - const options = { + const options = getDefaultNodeClientOptions({ dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', tracesSampleRate: 1.0, integrations: [new HttpIntegration({ tracing: true })], - }; + }); const hub = new Hub(new NodeClient(options, setupNodeTransport(options).transport)); addExtensionMethods(); @@ -97,7 +98,7 @@ describe('default protocols', () => { const p = new Promise(r => { resolve = r; }); - const options = { + const options = getDefaultNodeClientOptions({ dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', integrations: [new HttpIntegration({ breadcrumbs: true })], beforeBreadcrumb: (b: Breadcrumb) => { @@ -106,7 +107,7 @@ describe('default protocols', () => { } return b; }, - }; + }); hub.bindClient(new NodeClient(options, setupNodeTransport(options).transport)); return p; diff --git a/packages/node/test/integrations/linkederrors.test.ts b/packages/node/test/integrations/linkederrors.test.ts index 2c2957bf6a69..4d18707be904 100644 --- a/packages/node/test/integrations/linkederrors.test.ts +++ b/packages/node/test/integrations/linkederrors.test.ts @@ -5,6 +5,7 @@ import { Event, NodeClient } from '../../src'; import { LinkedErrors } from '../../src/integrations/linkederrors'; import { nodeStackParser } from '../../src/stack-parser'; import { setupNodeTransport } from '../../src/transports'; +import { getDefaultNodeClientOptions } from '../helper/node-client-options'; const stackParser = createStackParser(nodeStackParser); @@ -32,7 +33,7 @@ describe('LinkedErrors', () => { expect.assertions(2); const spy = jest.spyOn(linkedErrors, '_walkErrorTree'); const one = new Error('originalException'); - const options = { stackParser }; + const options = getDefaultNodeClientOptions({ stackParser }); const client = new NodeClient(options, setupNodeTransport(options).transport); let event: Event | undefined; return client @@ -56,7 +57,7 @@ describe('LinkedErrors', () => { }), ); const one = new Error('originalException'); - const options = { stackParser }; + const options = getDefaultNodeClientOptions({ stackParser }); const client = new NodeClient(options, setupNodeTransport(options).transport); return client.eventFromException(one).then(event => linkedErrors @@ -77,7 +78,7 @@ describe('LinkedErrors', () => { one.cause = two; two.cause = three; - const options = { stackParser }; + const options = getDefaultNodeClientOptions({ stackParser }); const client = new NodeClient(options, setupNodeTransport(options).transport); return client.eventFromException(one).then(event => linkedErrors @@ -111,7 +112,7 @@ describe('LinkedErrors', () => { one.reason = two; two.reason = three; - const options = { stackParser }; + const options = getDefaultNodeClientOptions({ stackParser }); const client = new NodeClient(options, setupNodeTransport(options).transport); return client.eventFromException(one).then(event => linkedErrors @@ -145,7 +146,7 @@ describe('LinkedErrors', () => { one.cause = two; two.cause = three; - const options = { stackParser }; + const options = getDefaultNodeClientOptions({ stackParser }); const client = new NodeClient(options, setupNodeTransport(options).transport); return client.eventFromException(one).then(event => linkedErrors diff --git a/packages/node/test/sdk.test.ts b/packages/node/test/sdk.test.ts new file mode 100644 index 000000000000..48e5accee439 --- /dev/null +++ b/packages/node/test/sdk.test.ts @@ -0,0 +1,92 @@ +import { Integration } from '@sentry/types'; + +import { init } from '../src/sdk'; +import * as sdk from '../src/sdk'; + +// eslint-disable-next-line no-var +declare var global: any; + +const PUBLIC_DSN = 'https://username@domain/123'; + +class MockIntegration implements Integration { + public name: string; + public setupOnce: jest.Mock = jest.fn(); + public constructor(name: string) { + this.name = name; + } +} + +const defaultIntegrationsBackup = sdk.defaultIntegrations; + +describe('init()', () => { + beforeEach(() => { + global.__SENTRY__ = {}; + }); + + afterEach(() => { + // @ts-ignore - Reset the default integrations of node sdk to original + sdk.defaultIntegrations = defaultIntegrationsBackup; + }); + + it("doesn't install default integrations if told not to", () => { + const mockDefaultIntegrations = [ + new MockIntegration('Mock integration 1.1'), + new MockIntegration('Mock integration 1.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + init({ dsn: PUBLIC_DSN, defaultIntegrations: false }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + }); + + it('installs merged default integrations, with overrides provided through options', () => { + const mockDefaultIntegrations = [ + new MockIntegration('Some mock integration 2.1'), + new MockIntegration('Some mock integration 2.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + const mockIntegrations = [ + new MockIntegration('Some mock integration 2.1'), + new MockIntegration('Some mock integration 2.3'), + ]; + + init({ dsn: PUBLIC_DSN, integrations: mockIntegrations }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + }); + + it('installs integrations returned from a callback function', () => { + const mockDefaultIntegrations = [ + new MockIntegration('Some mock integration 3.1'), + new MockIntegration('Some mock integration 3.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + const newIntegration = new MockIntegration('Some mock integration 3.3'); + + init({ + dsn: PUBLIC_DSN, + integrations: integrations => { + const newIntegrations = [...integrations]; + newIntegrations[1] = newIntegration; + return newIntegrations; + }, + }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/node/test/transports/setup.test.ts b/packages/node/test/transports/setup.test.ts index 6864ac89ea1e..38f99a4c95f3 100644 --- a/packages/node/test/transports/setup.test.ts +++ b/packages/node/test/transports/setup.test.ts @@ -3,6 +3,7 @@ import { FakeTransport } from '@sentry/core/test/mocks/transport'; import { HTTPSTransport, HTTPTransport, setupNodeTransport } from '@sentry/node/src/transports'; import { makeNodeTransport } from '../../src/transports/new'; +import { getDefaultNodeClientOptions } from '../helper/node-client-options'; jest.mock('../../src/transports/new', () => { const original = jest.requireActual('../../src/transports/new'); @@ -31,7 +32,7 @@ describe('setupNodeTransport', () => { }); it('returns the instantiated transport passed via the options', () => { - const options = { dsn: DSN, transport: FakeTransport }; + const options = getDefaultNodeClientOptions({ dsn: DSN, transport: FakeTransport }); const { transport, newTransport } = setupNodeTransport(options); expect(transport).toBeDefined(); diff --git a/packages/tracing/src/hubextensions.ts b/packages/tracing/src/hubextensions.ts index bd38b270cc22..80bd7a5783c8 100644 --- a/packages/tracing/src/hubextensions.ts +++ b/packages/tracing/src/hubextensions.ts @@ -1,5 +1,6 @@ import { getMainCarrier, Hub } from '@sentry/hub'; import { + ClientOptions, CustomSamplingContext, Integration, IntegrationClass, @@ -41,7 +42,11 @@ function traceHeaders(this: Hub): { [key: string]: string } { * * @returns The given transaction with its `sampled` value set */ -function sample(transaction: T, options: Options, samplingContext: SamplingContext): T { +function sample( + transaction: T, + options: Pick, + samplingContext: SamplingContext, +): T { // nothing to do if tracing is not enabled if (!hasTracingEnabled(options)) { transaction.sampled = false; @@ -171,7 +176,7 @@ function _startTransaction( customSamplingContext?: CustomSamplingContext, ): Transaction { const client = this.getClient(); - const options = (client && client.getOptions()) || {}; + const options: Partial = (client && client.getOptions()) || {}; let transaction = new Transaction(transactionContext, this); transaction = sample(transaction, options, { @@ -196,7 +201,7 @@ export function startIdleTransaction( customSamplingContext?: CustomSamplingContext, ): IdleTransaction { const client = hub.getClient(); - const options = (client && client.getOptions()) || {}; + const options: Partial = (client && client.getOptions()) || {}; let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, onScope); transaction = sample(transaction, options, { diff --git a/packages/tracing/src/utils.ts b/packages/tracing/src/utils.ts index 63598c00ac58..9d999df486fb 100644 --- a/packages/tracing/src/utils.ts +++ b/packages/tracing/src/utils.ts @@ -20,7 +20,9 @@ export { TRACEPARENT_REGEXP, extractTraceparentData } from '@sentry/utils'; * * Tracing is enabled when at least one of `tracesSampleRate` and `tracesSampler` is defined in the SDK config. */ -export function hasTracingEnabled(maybeOptions?: Options | undefined): boolean { +export function hasTracingEnabled( + maybeOptions?: Pick | undefined, +): boolean { const client = getCurrentHub().getClient(); const options = maybeOptions || (client && client.getOptions()); return !!options && ('tracesSampleRate' in options || 'tracesSampler' in options); diff --git a/packages/tracing/test/browser/backgroundtab.test.ts b/packages/tracing/test/browser/backgroundtab.test.ts index 440eb785a609..29612b410322 100644 --- a/packages/tracing/test/browser/backgroundtab.test.ts +++ b/packages/tracing/test/browser/backgroundtab.test.ts @@ -1,5 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { setupBrowserTransport } from '@sentry/browser/src/transports'; +import { getDefaultBrowserClientOptions } from '@sentry/browser/test/unit/helper/browser-client-options'; import { Hub, makeMain } from '@sentry/hub'; import { JSDOM } from 'jsdom'; @@ -14,7 +15,7 @@ describe('registerBackgroundTabDetection', () => { // @ts-ignore need to override global document global.document = dom.window.document; - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); diff --git a/packages/tracing/test/browser/browsertracing.test.ts b/packages/tracing/test/browser/browsertracing.test.ts index dfa4e0436b8c..76ccaf947271 100644 --- a/packages/tracing/test/browser/browsertracing.test.ts +++ b/packages/tracing/test/browser/browsertracing.test.ts @@ -1,5 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { setupBrowserTransport } from '@sentry/browser/src/transports'; +import { getDefaultBrowserClientOptions } from '@sentry/browser/test/unit/helper/browser-client-options'; import { Hub, makeMain } from '@sentry/hub'; import { getGlobalObject, InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; import { JSDOM } from 'jsdom'; @@ -52,7 +53,7 @@ describe('BrowserTracing', () => { let hub: Hub; beforeEach(() => { jest.useFakeTimers(); - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); document.head.innerHTML = ''; @@ -474,7 +475,7 @@ describe('BrowserTracing', () => { getGlobalObject().location = dogParkLocation as any; const tracesSampler = jest.fn(); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); hub.bindClient(new BrowserClient(options, setupBrowserTransport(options).transport)); // setting up the BrowserTracing integration automatically starts a pageload transaction createBrowserTracing(true); @@ -491,7 +492,7 @@ describe('BrowserTracing', () => { getGlobalObject().location = dogParkLocation as any; const tracesSampler = jest.fn(); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); hub.bindClient(new BrowserClient(options, setupBrowserTransport(options).transport)); // setting up the BrowserTracing integration normally automatically starts a pageload transaction, but that's not // what we're testing here diff --git a/packages/tracing/test/browser/request.test.ts b/packages/tracing/test/browser/request.test.ts index 65823e293351..4ead6fe5bb9b 100644 --- a/packages/tracing/test/browser/request.test.ts +++ b/packages/tracing/test/browser/request.test.ts @@ -1,5 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { setupBrowserTransport } from '@sentry/browser/src/transports'; +import { getDefaultBrowserClientOptions } from '@sentry/browser/test/unit/helper/browser-client-options'; import { Hub, makeMain } from '@sentry/hub'; import * as utils from '@sentry/utils'; @@ -73,7 +74,7 @@ describe('callbacks', () => { }; beforeAll(() => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); }); diff --git a/packages/tracing/test/errors.test.ts b/packages/tracing/test/errors.test.ts index c15825fd5480..8dbcb2454539 100644 --- a/packages/tracing/test/errors.test.ts +++ b/packages/tracing/test/errors.test.ts @@ -1,5 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { setupBrowserTransport } from '@sentry/browser/src/transports'; +import { NoopTransport } from '@sentry/core/src/transports/noop'; import { Hub, makeMain } from '@sentry/hub'; import { InstrumentHandlerCallback, InstrumentHandlerType } from '@sentry/utils'; @@ -35,7 +36,7 @@ describe('registerErrorHandlers()', () => { let hub: Hub; beforeEach(() => { mockAddInstrumentationHandler.mockClear(); - const options = { tracesSampleRate: 1 }; + const options = { tracesSampleRate: 1, transport: NoopTransport, integrations: [], stackParser: () => [] }; hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); }); diff --git a/packages/tracing/test/hub.test.ts b/packages/tracing/test/hub.test.ts index 045d9fa96fb1..6df8bad175c1 100644 --- a/packages/tracing/test/hub.test.ts +++ b/packages/tracing/test/hub.test.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { BrowserClient } from '@sentry/browser'; import { setupBrowserTransport } from '@sentry/browser/src/transports'; +import { getDefaultBrowserClientOptions } from '@sentry/browser/test/unit/helper/browser-client-options'; import { Hub, makeMain } from '@sentry/hub'; import * as utilsModule from '@sentry/utils'; // for mocking import { logger } from '@sentry/utils'; @@ -33,7 +34,7 @@ describe('Hub', () => { describe('getTransaction()', () => { it('should find a transaction which has been set on the scope if sampled = true', () => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -47,7 +48,7 @@ describe('Hub', () => { }); it('should find a transaction which has been set on the scope if sampled = false', () => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark', sampled: false }); @@ -60,7 +61,7 @@ describe('Hub', () => { }); it("should not find an open transaction if it's not on the scope", () => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -73,7 +74,7 @@ describe('Hub', () => { describe('default sample context', () => { it('should add transaction context data to default sample context', () => { const tracesSampler = jest.fn(); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); @@ -90,7 +91,7 @@ describe('Hub', () => { it("should add parent's sampling decision to default sample context", () => { const tracesSampler = jest.fn(); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const parentSamplingDecsion = false; @@ -109,7 +110,7 @@ describe('Hub', () => { describe('sample()', () => { it('should set sampled = false when tracing is disabled', () => { - const options = {}; + const options = getDefaultBrowserClientOptions({}); // neither tracesSampleRate nor tracesSampler is defined -> tracing disabled const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); @@ -119,7 +120,7 @@ describe('Hub', () => { }); it('should set sampled = false if tracesSampleRate is 0', () => { - const options = { tracesSampleRate: 0 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -128,7 +129,7 @@ describe('Hub', () => { }); it('should set sampled = true if tracesSampleRate is 1', () => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -137,7 +138,7 @@ describe('Hub', () => { }); it('should set sampled = true if tracesSampleRate is 1 (without global hub)', () => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -146,7 +147,7 @@ describe('Hub', () => { it("should call tracesSampler if it's defined", () => { const tracesSampler = jest.fn(); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -156,7 +157,7 @@ describe('Hub', () => { it('should set sampled = false if tracesSampler returns 0', () => { const tracesSampler = jest.fn().mockReturnValue(0); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -167,7 +168,7 @@ describe('Hub', () => { it('should set sampled = true if tracesSampler returns 1', () => { const tracesSampler = jest.fn().mockReturnValue(1); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -178,7 +179,7 @@ describe('Hub', () => { it('should set sampled = true if tracesSampler returns 1 (without global hub)', () => { const tracesSampler = jest.fn().mockReturnValue(1); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -189,7 +190,7 @@ describe('Hub', () => { it('should not try to override explicitly set positive sampling decision', () => { // so that the decision otherwise would be false const tracesSampler = jest.fn().mockReturnValue(0); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark', sampled: true }); @@ -200,7 +201,7 @@ describe('Hub', () => { it('should not try to override explicitly set negative sampling decision', () => { // so that the decision otherwise would be true const tracesSampler = jest.fn().mockReturnValue(1); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark', sampled: false }); @@ -211,7 +212,7 @@ describe('Hub', () => { it('should prefer tracesSampler to tracesSampleRate', () => { // make the two options do opposite things to prove precedence const tracesSampler = jest.fn().mockReturnValue(true); - const options = { tracesSampleRate: 0, tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0, tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -222,7 +223,7 @@ describe('Hub', () => { it('should tolerate tracesSampler returning a boolean', () => { const tracesSampler = jest.fn().mockReturnValue(true); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -233,7 +234,7 @@ describe('Hub', () => { it('should record sampling method when sampling decision is explicitly set', () => { const tracesSampler = jest.fn().mockReturnValue(0.1121); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark', sampled: true }); @@ -245,7 +246,7 @@ describe('Hub', () => { it('should record sampling method and rate when sampling decision comes from tracesSampler', () => { const tracesSampler = jest.fn().mockReturnValue(0.1121); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -256,7 +257,7 @@ describe('Hub', () => { }); it('should record sampling method when sampling decision is inherited', () => { - const options = { tracesSampleRate: 0.1121 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0.1121 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark', parentSampled: true }); @@ -267,7 +268,7 @@ describe('Hub', () => { }); it('should record sampling method and rate when sampling decision comes from traceSampleRate', () => { - const options = { tracesSampleRate: 0.1121 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0.1121 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -280,7 +281,7 @@ describe('Hub', () => { describe('isValidSampleRate()', () => { it("should reject tracesSampleRates which aren't numbers or booleans", () => { - const options = { tracesSampleRate: 'dogs!' as any }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 'dogs!' as any }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -289,7 +290,7 @@ describe('Hub', () => { }); it('should reject tracesSampleRates which are NaN', () => { - const options = { tracesSampleRate: 'dogs!' as any }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 'dogs!' as any }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -299,7 +300,7 @@ describe('Hub', () => { // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 it('should reject tracesSampleRates less than 0', () => { - const options = { tracesSampleRate: -26 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: -26 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -309,7 +310,7 @@ describe('Hub', () => { // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 it('should reject tracesSampleRates greater than 1', () => { - const options = { tracesSampleRate: 26 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 26 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -319,7 +320,7 @@ describe('Hub', () => { it("should reject tracesSampler return values which aren't numbers or booleans", () => { const tracesSampler = jest.fn().mockReturnValue('dogs!'); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -329,7 +330,7 @@ describe('Hub', () => { it('should reject tracesSampler return values which are NaN', () => { const tracesSampler = jest.fn().mockReturnValue(NaN); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -340,7 +341,7 @@ describe('Hub', () => { // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 it('should reject tracesSampler return values less than 0', () => { const tracesSampler = jest.fn().mockReturnValue(-12); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -351,7 +352,7 @@ describe('Hub', () => { // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 it('should reject tracesSampler return values greater than 1', () => { const tracesSampler = jest.fn().mockReturnValue(31); - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); hub.startTransaction({ name: 'dogpark' }); @@ -361,7 +362,7 @@ describe('Hub', () => { }); it('should drop transactions with sampled = false', () => { - const options = { tracesSampleRate: 0 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0 }); const client = new BrowserClient(options, setupBrowserTransport(options).transport); jest.spyOn(client, 'captureEvent'); @@ -379,7 +380,7 @@ describe('Hub', () => { describe('sampling inheritance', () => { it('should propagate sampling decision to child spans', () => { - const options = { tracesSampleRate: Math.random() }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: Math.random() }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const transaction = hub.startTransaction({ name: 'dogpark' }); @@ -392,11 +393,11 @@ describe('Hub', () => { testOnlyIfNodeVersionAtLeast(10)( 'should propagate positive sampling decision to child transactions in XHR header', async () => { - const options = { + const options = getDefaultBrowserClientOptions({ dsn: 'https://1231@dogs.are.great/1121', tracesSampleRate: 1, integrations: [new BrowserTracing()], - }; + }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); @@ -433,11 +434,11 @@ describe('Hub', () => { testOnlyIfNodeVersionAtLeast(10)( 'should propagate negative sampling decision to child transactions in XHR header', async () => { - const options = { + const options = getDefaultBrowserClientOptions({ dsn: 'https://1231@dogs.are.great/1121', tracesSampleRate: 1, integrations: [new BrowserTracing()], - }; + }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); @@ -483,7 +484,7 @@ describe('Hub', () => { // sample rate), so make parent's decision the opposite to prove that inheritance takes precedence over // tracesSampleRate mathRandom.mockReturnValueOnce(1); - const options = { tracesSampleRate: 0.5 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0.5 }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); const parentSamplingDecsion = true; @@ -498,7 +499,7 @@ describe('Hub', () => { }); it("should inherit parent's negative sampling decision if tracesSampler is undefined", () => { - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); // tracesSampleRate = 1 means every transaction should end up with sampled = true, so make parent's decision the // opposite to prove that inheritance takes precedence over tracesSampleRate const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); @@ -520,7 +521,7 @@ describe('Hub', () => { const tracesSampler = () => true; const parentSamplingDecsion = false; - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); @@ -539,7 +540,7 @@ describe('Hub', () => { const tracesSampler = () => false; const parentSamplingDecsion = true; - const options = { tracesSampler }; + const options = getDefaultBrowserClientOptions({ tracesSampler }); const hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); makeMain(hub); diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/tracing/test/idletransaction.test.ts index b8029c6b92d7..b040f4adbd9b 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/tracing/test/idletransaction.test.ts @@ -1,4 +1,5 @@ import { BrowserClient, Transports } from '@sentry/browser'; +import { getDefaultBrowserClientOptions } from '@sentry/browser/test/unit/helper/browser-client-options'; import { Hub } from '@sentry/hub'; import { @@ -16,8 +17,8 @@ class SimpleTransport extends Transports.BaseTransport {} const dsn = 'https://123@sentry.io/42'; let hub: Hub; beforeEach(() => { - const options = { dsn, tracesSampleRate: 1, transport: SimpleTransport }; - hub = new Hub(new BrowserClient(options, new SimpleTransport(options))); + const options = getDefaultBrowserClientOptions({ dsn, tracesSampleRate: 1, transport: SimpleTransport }); + hub = new Hub(new BrowserClient(options, new SimpleTransport({ dsn }))); }); describe('IdleTransaction', () => { diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index fe11fbf143ee..13f47c51ae7b 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -1,5 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { setupBrowserTransport } from '@sentry/browser/src/transports'; +import { getDefaultBrowserClientOptions } from '@sentry/browser/test/unit/helper/browser-client-options'; import { Hub, makeMain, Scope } from '@sentry/hub'; import { Span, Transaction } from '../src'; @@ -10,7 +11,7 @@ describe('Span', () => { beforeEach(() => { const myScope = new Scope(); - const options = { tracesSampleRate: 1 }; + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport), myScope); makeMain(hub); }); @@ -218,10 +219,10 @@ describe('Span', () => { }); test('maxSpans correctly limits number of spans', () => { - const options = { + const options = getDefaultBrowserClientOptions({ _experiments: { maxSpans: 3 }, tracesSampleRate: 1, - }; + }); const _hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); const spy = jest.spyOn(_hub as any, 'captureEvent') as any; const transaction = _hub.startTransaction({ name: 'test' }); @@ -234,9 +235,9 @@ describe('Span', () => { }); test('no span recorder created if transaction.sampled is false', () => { - const options = { + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1, - }; + }); const _hub = new Hub(new BrowserClient(options, setupBrowserTransport(options).transport)); const spy = jest.spyOn(_hub as any, 'captureEvent') as any; const transaction = _hub.startTransaction({ name: 'test', sampled: false }); diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index c3f2a9920258..f0cdc8b3a988 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -1,7 +1,7 @@ import { DsnComponents } from './dsn'; import { Event, EventHint } from './event'; import { Integration, IntegrationClass } from './integration'; -import { Options } from './options'; +import { ClientOptions } from './options'; import { Scope } from './scope'; import { Session } from './session'; import { Severity, SeverityLevel } from './severity'; @@ -16,7 +16,7 @@ import { Transport } from './transport'; * there will only be one instance during runtime. * */ -export interface Client { +export interface Client { /** * Captures an exception event and sends it to Sentry. * diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 84430c1d3976..5fe4ced6375b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -27,7 +27,7 @@ export type { Hub } from './hub'; export type { Integration, IntegrationClass } from './integration'; export type { Mechanism } from './mechanism'; export type { ExtractedNodeRequestData, Primitive, WorkerLocation } from './misc'; -export type { Options } from './options'; +export type { ClientOptions, Options } from './options'; export type { Package } from './package'; export type { QueryParams, Request, SentryRequest, SentryRequestType } from './request'; export type { Response } from './response'; diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index a7306acdd76f..8bd273752700 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -7,8 +7,7 @@ import { StackLineParser, StackParser } from './stacktrace'; import { SamplingContext } from './transaction'; import { Transport, TransportClass, TransportOptions } from './transport'; -/** Base configuration options for every SDK. */ -export interface Options { +export interface ClientOptions { /** * Enable debug functionality in the SDK itself */ @@ -20,6 +19,21 @@ export interface Options { */ enabled?: boolean; + /** Attaches stacktraces to pure capture message / log integrations */ + attachStacktrace?: boolean; + + /** + * A flag enabling Sessions Tracking feature. + * By default, Sessions Tracking is enabled. + */ + autoSessionTracking?: boolean; + + /** + * Send SDK Client Reports. + * By default, Client Reports are enabled. + */ + sendClientReports?: boolean; + /** * The Dsn used to connect to Sentry and identify the project. If omitted, the * SDK will not send any data to Sentry. @@ -27,29 +41,33 @@ export interface Options { dsn?: string; /** - * If this is set to false, default integrations will not be added, otherwise this will internally be set to the - * recommended default integrations. - * TODO: We should consider changing this to `boolean | Integration[]` + * The release identifier used when uploading respective source maps. Specify + * this value to allow Sentry to resolve the correct source maps when + * processing events. */ - defaultIntegrations?: false | Integration[]; + release?: string; + + /** The current environment of your application (e.g. "production"). */ + environment?: string; + + /** Sets the distribution for all events */ + dist?: string; /** * List of integrations that should be installed after SDK was initialized. - * Accepts either a list of integrations or a function that receives - * default integrations and returns a new, updated list. */ - integrations?: Integration[] | ((integrations: Integration[]) => Integration[]); + integrations: Integration[]; /** - * A pattern for error messages which should not be sent to Sentry. - * By default, all errors will be sent. + * Transport object that should be used to send events to Sentry */ - ignoreErrors?: Array; + transport: TransportClass; /** - * Transport object that should be used to send events to Sentry + * A stack parser implementation + * By default, a stack parser is supplied for all supported platforms */ - transport?: TransportClass; + stackParser: StackParser; /** * Options for the default transport that the SDK uses. @@ -57,24 +75,20 @@ export interface Options { transportOptions?: TransportOptions; /** - * A URL to an envelope tunnel endpoint. An envelope tunnel is an HTTP endpoint - * that accepts Sentry envelopes for forwarding. This can be used to force data - * through a custom server independent of the type of data. + * Sample rate to determine trace sampling. + * + * 0.0 = 0% chance of a given trace being sent (send no traces) 1.0 = 100% chance of a given trace being sent (send + * all traces) + * + * Tracing is enabled if either this or `tracesSampler` is defined. If both are defined, `tracesSampleRate` is + * ignored. */ - tunnel?: string; + tracesSampleRate?: number; /** - * The release identifier used when uploading respective source maps. Specify - * this value to allow Sentry to resolve the correct source maps when - * processing events. + * Initial data to populate scope. */ - release?: string; - - /** The current environment of your application (e.g. "production"). */ - environment?: string; - - /** Sets the distribution for all events */ - dist?: string; + initialScope?: CaptureContext; /** * The maximum number of breadcrumbs sent with events. Defaults to 100. @@ -85,9 +99,6 @@ export interface Options { /** A global sample rate to apply to all events (0 - 1). */ sampleRate?: number; - /** Attaches stacktraces to pure capture message / log integrations */ - attachStacktrace?: boolean; - /** Maximum number of chars a single value can have before it will be truncated. */ maxValueLength?: number; @@ -123,38 +134,17 @@ export interface Options { shutdownTimeout?: number; /** - * Sample rate to determine trace sampling. - * - * 0.0 = 0% chance of a given trace being sent (send no traces) 1.0 = 100% chance of a given trace being sent (send - * all traces) - * - * Tracing is enabled if either this or `tracesSampler` is defined. If both are defined, `tracesSampleRate` is - * ignored. - */ - tracesSampleRate?: number; - - /** - * A flag enabling Sessions Tracking feature. - * By default, Sessions Tracking is enabled. - */ - autoSessionTracking?: boolean; - - /** - * Send SDK Client Reports. - * By default, Client Reports are enabled. - */ - sendClientReports?: boolean; - - /** - * Initial data to populate scope. + * A pattern for error messages which should not be sent to Sentry. + * By default, all errors will be sent. */ - initialScope?: CaptureContext; + ignoreErrors?: Array; /** - * A stack parser implementation or an array of stack line parsers - * By default, a stack parser is supplied for all supported browsers + * A URL to an envelope tunnel endpoint. An envelope tunnel is an HTTP endpoint + * that accepts Sentry envelopes for forwarding. This can be used to force data + * through a custom server independent of the type of data. */ - stackParser?: StackParser | StackLineParser[]; + tunnel?: string; /** * Set of metadata about the SDK that can be internally used to enhance envelopes and events, @@ -210,3 +200,31 @@ export interface Options { */ beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | null; } + +/** Base configuration options for every SDK. */ +export interface Options extends Omit, 'integrations' | 'transport' | 'stackParser'> { + /** + * If this is set to false, default integrations will not be added, otherwise this will internally be set to the + * recommended default integrations. + * TODO: We should consider changing this to `boolean | Integration[]` + */ + defaultIntegrations?: false | Integration[]; + + /** + * List of integrations that should be installed after SDK was initialized. + * Accepts either a list of integrations or a function that receives + * default integrations and returns a new, updated list. + */ + integrations?: Integration[] | ((integrations: Integration[]) => Integration[]); + + /** + * Transport object that should be used to send events to Sentry + */ + transport?: TransportClass; + + /** + * A stack parser implementation or an array of stack line parsers + * By default, a stack parser is supplied for all supported browsers + */ + stackParser?: StackParser | StackLineParser[]; +} diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 5c500c5ec654..56b864e2863b 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -98,8 +98,6 @@ function validateDsn(dsn: DsnComponents): boolean | void { /** The Sentry Dsn, identifying a Sentry instance and project. */ export function makeDsn(from: DsnLike): DsnComponents { const components = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from); - validateDsn(components); - return components; }