diff --git a/packages/browser/src/backend.ts b/packages/browser/src/backend.ts deleted file mode 100644 index 5396a8b3dab6..000000000000 --- a/packages/browser/src/backend.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { BaseBackend } from '@sentry/core'; -import { Event, EventHint, Options, SeverityLevel, Transport } from '@sentry/types'; -import { supportsFetch } from '@sentry/utils'; - -import { eventFromException, eventFromMessage } from './eventbuilder'; -import { FetchTransport, XHRTransport } from './transports'; - -/** - * Configuration options for the Sentry Browser SDK. - * @see BrowserClient for more information. - */ -export interface BrowserOptions extends Options { - /** - * A pattern for error URLs which should exclusively be sent to Sentry. - * This is the opposite of {@link Options.denyUrls}. - * By default, all errors will be sent. - */ - allowUrls?: Array; - - /** - * A pattern for error URLs which should not be sent to Sentry. - * To allow certain errors instead, use {@link Options.allowUrls}. - * By default, all errors will be sent. - */ - denyUrls?: Array; - - /** @deprecated use {@link Options.allowUrls} instead. */ - whitelistUrls?: Array; - - /** @deprecated use {@link Options.denyUrls} instead. */ - blacklistUrls?: Array; -} - -/** - * The Sentry Browser SDK Backend. - * @hidden - */ -export class BrowserBackend extends BaseBackend { - /** - * @inheritDoc - */ - public eventFromException(exception: unknown, hint?: EventHint): PromiseLike { - return eventFromException(this._options, exception, hint); - } - /** - * @inheritDoc - */ - public eventFromMessage(message: string, level: SeverityLevel = 'info', hint?: EventHint): PromiseLike { - return eventFromMessage(this._options, message, level, hint); - } - - /** - * @inheritDoc - */ - protected _setupTransport(): Transport { - if (!this._options.dsn) { - // We return the noop transport here in case there is no Dsn. - return super._setupTransport(); - } - - const transportOptions = { - ...this._options.transportOptions, - dsn: this._options.dsn, - tunnel: this._options.tunnel, - sendClientReports: this._options.sendClientReports, - _metadata: this._options._metadata, - }; - - if (this._options.transport) { - return new this._options.transport(transportOptions); - } - if (supportsFetch()) { - return new FetchTransport(transportOptions); - } - return new XHRTransport(transportOptions); - } -} diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index aeb1539e2a7d..29834e28954f 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -1,10 +1,37 @@ import { BaseClient, Scope, SDK_VERSION } from '@sentry/core'; -import { Event, EventHint } from '@sentry/types'; -import { getGlobalObject, logger } from '@sentry/utils'; +import { Event, EventHint, Options, SeverityLevel, Transport } from '@sentry/types'; +import { getGlobalObject, logger, supportsFetch } from '@sentry/utils'; -import { BrowserBackend, BrowserOptions } from './backend'; +import { eventFromException, eventFromMessage } from './eventbuilder'; import { injectReportDialog, ReportDialogOptions } from './helpers'; import { Breadcrumbs } from './integrations'; +import { FetchTransport, XHRTransport } from './transports'; + +/** + * Configuration options for the Sentry Browser SDK. + * @see BrowserClient for more information. + */ +export interface BrowserOptions extends Options { + /** + * A pattern for error URLs which should exclusively be sent to Sentry. + * This is the opposite of {@link Options.denyUrls}. + * By default, all errors will be sent. + */ + allowUrls?: Array; + + /** + * A pattern for error URLs which should not be sent to Sentry. + * To allow certain errors instead, use {@link Options.allowUrls}. + * By default, all errors will be sent. + */ + denyUrls?: Array; + + /** @deprecated use {@link Options.allowUrls} instead. */ + whitelistUrls?: Array; + + /** @deprecated use {@link Options.denyUrls} instead. */ + blacklistUrls?: Array; +} /** * The Sentry Browser SDK Client. @@ -12,7 +39,7 @@ import { Breadcrumbs } from './integrations'; * @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. * @@ -31,7 +58,7 @@ export class BrowserClient extends BaseClient { version: SDK_VERSION, }; - super(BrowserBackend, options); + super(options); } /** @@ -75,4 +102,45 @@ export class BrowserClient extends BaseClient { } super._sendEvent(event); } + + /** + * @inheritDoc + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + protected _eventFromException(exception: any, hint?: EventHint): PromiseLike { + return eventFromException(this._options, exception, hint); + } + + /** + * @inheritDoc + */ + protected _eventFromMessage(message: string, level: SeverityLevel = 'info', hint?: EventHint): PromiseLike { + return eventFromMessage(this._options, message, level, hint); + } + + /** + * @inheritDoc + */ + protected _setupTransport(): Transport | undefined { + if (!this._options.dsn) { + // We return the noop transport here in case there is no Dsn. + return undefined; + } + + const transportOptions = { + ...this._options.transportOptions, + dsn: this._options.dsn, + tunnel: this._options.tunnel, + sendClientReports: this._options.sendClientReports, + _metadata: this._options._metadata, + }; + + if (this._options.transport) { + return new this._options.transport(transportOptions); + } + if (supportsFetch()) { + return new FetchTransport(transportOptions); + } + return new XHRTransport(transportOptions); + } } diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 60cce87af08a..6c59e838237d 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -38,8 +38,7 @@ export { withScope, } from '@sentry/core'; -export { BrowserOptions } from './backend'; -export { BrowserClient } from './client'; +export { BrowserClient, BrowserOptions } from './client'; export { injectReportDialog, ReportDialogOptions } from './helpers'; export { eventFromException, eventFromMessage } from './eventbuilder'; export { defaultIntegrations, forceLoad, init, lastEventId, onLoad, showReportDialog, flush, close, wrap } from './sdk'; diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index c980d986d3b3..c83eecffa028 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,8 +1,7 @@ import { getCurrentHub, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; import { addInstrumentationHandler, getGlobalObject, logger, resolvedSyncPromise } from '@sentry/utils'; -import { BrowserOptions } from './backend'; -import { BrowserClient } from './client'; +import { BrowserClient, BrowserOptions } from './client'; import { ReportDialogOptions, wrap as internalWrap } from './helpers'; import { Breadcrumbs, Dedupe, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations'; diff --git a/packages/browser/test/unit/backend.test.ts b/packages/browser/test/unit/backend.test.ts deleted file mode 100644 index 71ffec4d75b1..000000000000 --- a/packages/browser/test/unit/backend.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BrowserBackend } from '../../src/backend'; - -let backend: BrowserBackend; - -describe('BrowserBackend', () => { - describe('sendEvent()', () => { - it('should use NoopTransport', () => { - backend = new BrowserBackend({}); - expect(backend.getTransport().constructor.name).toBe('NoopTransport'); - }); - }); -}); diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index f5f4554c552a..0f04fbbef21d 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -246,7 +246,7 @@ describe('SentryBrowser initialization', () => { it('should set SDK data when Sentry.init() is called', () => { init({ dsn }); - const sdkData = (getCurrentHub().getClient() as any)._backend._transport._api.metadata?.sdk; + const sdkData = (getCurrentHub().getClient() as any)._transport._api.metadata?.sdk; expect(sdkData.name).toBe('sentry.javascript.browser'); expect(sdkData.packages[0].name).toBe('npm:@sentry/browser'); @@ -257,7 +257,7 @@ describe('SentryBrowser initialization', () => { it('should set SDK data when instantiating a client directly', () => { const client = new BrowserClient({ dsn }); - const sdkData = (client as any)._backend._transport._api.metadata?.sdk; + const sdkData = (client as any)._transport._api.metadata?.sdk; expect(sdkData.name).toBe('sentry.javascript.browser'); expect(sdkData.packages[0].name).toBe('npm:@sentry/browser'); @@ -285,7 +285,7 @@ describe('SentryBrowser initialization', () => { }, }); - const sdkData = (getCurrentHub().getClient() as any)._backend._transport._api.metadata?.sdk; + const sdkData = (getCurrentHub().getClient() as any)._transport._api.metadata?.sdk; expect(sdkData.name).toBe('sentry.javascript.angular'); expect(sdkData.packages[0].name).toBe('npm:@sentry/angular'); diff --git a/packages/browser/test/unit/integrations/helpers.test.ts b/packages/browser/test/unit/integrations/helpers.test.ts index 80c60021400d..4b68df34cd95 100644 --- a/packages/browser/test/unit/integrations/helpers.test.ts +++ b/packages/browser/test/unit/integrations/helpers.test.ts @@ -1,5 +1,5 @@ import { WrappedFunction } from '@sentry/types'; -import { SinonSpy, spy } from 'sinon'; +import { spy } from 'sinon'; import { wrap } from '../../../src/helpers'; diff --git a/packages/browser/test/unit/integrations/linkederrors.test.ts b/packages/browser/test/unit/integrations/linkederrors.test.ts index 1531aa6f77ed..ae118ac13132 100644 --- a/packages/browser/test/unit/integrations/linkederrors.test.ts +++ b/packages/browser/test/unit/integrations/linkederrors.test.ts @@ -1,6 +1,6 @@ import { ExtendedError } from '@sentry/types'; -import { BrowserBackend } from '../../../src/backend'; +import { eventFromException } from '../../../src'; import * as LinkedErrorsModule from '../../../src/integrations/linkederrors'; describe('LinkedErrors', () => { @@ -34,8 +34,7 @@ describe('LinkedErrors', () => { one.cause = two; const originalException = one; - const backend = new BrowserBackend({}); - return backend.eventFromException(originalException).then(event => { + return eventFromException({}, originalException).then(event => { const result = LinkedErrorsModule._handler('cause', 5, event, { originalException, }); @@ -64,8 +63,7 @@ describe('LinkedErrors', () => { one.reason = two; const originalException = one; - const backend = new BrowserBackend({}); - return backend.eventFromException(originalException).then(event => { + return eventFromException({}, originalException).then(event => { const result = LinkedErrorsModule._handler('reason', 5, event, { originalException, }); @@ -90,9 +88,8 @@ describe('LinkedErrors', () => { one.cause = two; two.cause = three; - const backend = new BrowserBackend({}); const originalException = one; - return backend.eventFromException(originalException).then(event => { + return eventFromException({}, originalException).then(event => { const result = LinkedErrorsModule._handler('cause', 2, event, { originalException, }); diff --git a/packages/core/src/basebackend.ts b/packages/core/src/basebackend.ts deleted file mode 100644 index ffdcd22af6c0..000000000000 --- a/packages/core/src/basebackend.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Event, EventHint, Options, Session, SeverityLevel, Transport } from '@sentry/types'; -import { logger, SentryError } from '@sentry/utils'; - -import { NoopTransport } from './transports/noop'; - -/** - * Internal platform-dependent Sentry SDK Backend. - * - * While {@link Client} contains business logic specific to an SDK, the - * Backend offers platform specific implementations for low-level operations. - * These are persisting and loading information, sending events, and hooking - * into the environment. - * - * Backends receive a handle to the Client in their constructor. When a - * Backend automatically generates events, it must pass them to - * the Client for validation and processing first. - * - * Usually, the Client will be of corresponding type, e.g. NodeBackend - * receives NodeClient. However, higher-level SDKs can choose to instantiate - * multiple Backends and delegate tasks between them. In this case, an event - * generated by one backend might very well be sent by another one. - * - * The client also provides access to options via {@link Client.getOptions}. - * @hidden - */ -export interface Backend { - /** Creates a {@link Event} from an exception. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - eventFromException(exception: any, hint?: EventHint): PromiseLike; - - /** Creates a {@link Event} from a plain message. */ - eventFromMessage(message: string, level?: SeverityLevel, hint?: EventHint): PromiseLike; - - /** Submits the event to Sentry */ - sendEvent(event: Event): void; - - /** Submits the session to Sentry */ - sendSession(session: Session): void; - - /** - * Returns the transport that is used by the backend. - * Please note that the transport gets lazy initialized so it will only be there once the first event has been sent. - * - * @returns The transport. - */ - getTransport(): Transport; -} - -/** - * A class object that can instantiate Backend objects. - * @hidden - */ -export type BackendClass = new (options: O) => B; - -/** - * This is the base implemention of a Backend. - * @hidden - */ -export abstract class BaseBackend implements Backend { - /** Options passed to the SDK. */ - protected readonly _options: O; - - /** Cached transport used internally. */ - protected _transport: Transport; - - /** Creates a new backend instance. */ - public constructor(options: O) { - this._options = options; - if (!this._options.dsn) { - logger.warn('No DSN provided, backend will not do anything.'); - } - this._transport = this._setupTransport(); - } - - /** - * @inheritDoc - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types - public eventFromException(_exception: any, _hint?: EventHint): PromiseLike { - throw new SentryError('Backend has to implement `eventFromException` method'); - } - - /** - * @inheritDoc - */ - public eventFromMessage(_message: string, _level?: SeverityLevel, _hint?: EventHint): PromiseLike { - throw new SentryError('Backend has to implement `eventFromMessage` method'); - } - - /** - * @inheritDoc - */ - public sendEvent(event: Event): void { - void this._transport.sendEvent(event).then(null, reason => { - logger.error(`Error while sending event: ${reason}`); - }); - } - - /** - * @inheritDoc - */ - public sendSession(session: Session): void { - if (!this._transport.sendSession) { - logger.warn("Dropping session because custom transport doesn't implement sendSession"); - return; - } - - void this._transport.sendSession(session).then(null, reason => { - logger.error(`Error while sending session: ${reason}`); - }); - } - - /** - * @inheritDoc - */ - public getTransport(): Transport { - return this._transport; - } - - /** - * Sets up the transport so it can be used later to send requests. - */ - protected _setupTransport(): Transport { - return new NoopTransport(); - } -} diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 8ff1bb60e739..7b3f180e305d 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -27,7 +27,6 @@ import { uuid4, } from '@sentry/utils'; -import { Backend, BackendClass } from './basebackend'; import { IntegrationIndex, setupIntegrations } from './integration'; const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured."; @@ -35,17 +34,15 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca /** * Base implementation for all JavaScript SDK clients. * - * Call the constructor with the corresponding backend constructor and options - * specific to the client subclass. To access these options later, use - * {@link Client.getOptions}. Also, the Backend instance is available via - * {@link Client.getBackend}. + * Call the constructor with the options specific to the client subclass. + * To access these options later, use {@link Client.getOptions}. * * If a Dsn is specified in the options, it will be parsed and stored. Use * {@link Client.getDsn} to retrieve the Dsn at any moment. In case the Dsn is * invalid, the constructor will throw a {@link SentryException}. Note that * without a valid Dsn, the SDK will not send any events to Sentry. * - * Before sending an event via the backend, it is passed through + * Before sending an event via the transport, it is passed through * {@link BaseClient._prepareEvent} to add SDK information and scope data * (breadcrumbs and context). To add more custom information, override this * method and extend the resulting prepared event. @@ -56,22 +53,15 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca * {@link Client.addBreadcrumb}. * * @example - * class NodeClient extends BaseClient { + * class NodeClient extends BaseClient { * public constructor(options: NodeOptions) { - * super(NodeBackend, options); + * super(options); * } * * // ... * } */ -export abstract class BaseClient implements Client { - /** - * The backend used to physically interact in the environment. Usually, this - * will correspond to the client. When composing SDKs, however, the Backend - * from the root SDK will be used. - */ - protected readonly _backend: B; - +export abstract class BaseClient implements Client { /** Options passed to the SDK. */ protected readonly _options: O; @@ -84,19 +74,22 @@ export abstract class BaseClient implement /** Number of calls being processed */ protected _numProcessing: number = 0; + /** Cached transport used internally. */ + protected _transport: Transport | undefined; + /** * Initializes this client instance. * - * @param backendClass A constructor function to create the backend. * @param options Options for the client. */ - protected constructor(backendClass: BackendClass, options: O) { - this._backend = new backendClass(options); + protected constructor(options: O) { this._options = options; if (options.dsn) { this._dsn = new Dsn(options.dsn); } + + this._transport = this._setupTransport(); } /** @@ -113,8 +106,7 @@ export abstract class BaseClient implement let eventId: string | undefined = hint && hint.event_id; this._process( - this._getBackend() - .eventFromException(exception, hint) + this._eventFromException(exception, hint) .then(event => this._captureEvent(event, hint, scope)) .then(result => { eventId = result; @@ -131,8 +123,8 @@ export abstract class BaseClient implement let eventId: string | undefined = hint && hint.event_id; const promisedEvent = isPrimitive(message) - ? this._getBackend().eventFromMessage(String(message), level, hint) - : this._getBackend().eventFromException(message, hint); + ? this._eventFromMessage(String(message), level, hint) + : this._eventFromException(message, hint); this._process( promisedEvent @@ -201,8 +193,8 @@ export abstract class BaseClient implement /** * @inheritDoc */ - public getTransport(): Transport { - return this._getBackend().getTransport(); + public getTransport(): Transport | undefined { + return this._transport; } /** @@ -210,9 +202,11 @@ export abstract class BaseClient implement */ public flush(timeout?: number): PromiseLike { return this._isClientDoneProcessing(timeout).then(clientFinished => { - return this.getTransport() - .close(timeout) - .then(transportFlushed => clientFinished && transportFlushed); + const transport = this.getTransport(); + if (transport) { + return transport.close(timeout).then(transportFlushed => clientFinished && transportFlushed); + } + return false; }); } @@ -247,6 +241,36 @@ export abstract class BaseClient implement } } + /** + * @inheritDoc + */ + protected _sendEvent(event: Event): void { + const transport = this.getTransport(); + if (transport) { + void transport.sendEvent(event).then(null, reason => { + logger.error(`Error while sending event: ${reason}`); + }); + } + } + + /** + * @inheritDoc + */ + protected _sendSession(session: Session): void { + const transport = this.getTransport(); + if (!transport) { + return; + } + if (!transport.sendSession) { + logger.warn("Dropping session because custom transport doesn't implement sendSession"); + return; + } + + void transport.sendSession(session).then(null, reason => { + logger.error(`Error while sending session: ${reason}`); + }); + } + /** Updates existing session based on the provided event */ protected _updateSessionFromEvent(session: Session, event: Event): void { let crashed = false; @@ -280,11 +304,6 @@ export abstract class BaseClient implement } } - /** Deliver captured session to Sentry */ - protected _sendSession(session: Session): void { - this._getBackend().sendSession(session); - } - /** * Determine if the client is finished processing. Returns a promise because it will wait `timeout` ms before saying * "no" (resolving to `false`) in order to give the client a chance to potentially finish first. @@ -315,11 +334,6 @@ export abstract class BaseClient implement }); } - /** Returns the current backend. */ - protected _getBackend(): B { - return this._backend; - } - /** Determines whether this SDK is enabled and a valid Dsn is present. */ protected _isEnabled(): boolean { return this.getOptions().enabled !== false && this._dsn !== undefined; @@ -479,14 +493,6 @@ export abstract class BaseClient implement } } - /** - * Tells the backend to send this event - * @param event The Sentry event to send - */ - protected _sendEvent(event: Event): void { - this._getBackend().sendEvent(event); - } - /** * Processes the event and logs an error in case of rejection * @param event @@ -527,7 +533,7 @@ export abstract class BaseClient implement type RecordLostEventParams = Parameters; function recordLostEvent(outcome: RecordLostEventParams[0], category: RecordLostEventParams[1]): void { - if (transport.recordLostEvent) { + if (transport && transport.recordLostEvent) { transport.recordLostEvent(outcome, category); } } @@ -611,6 +617,22 @@ export abstract class BaseClient implement }, ); } + + /** + * Sets up the transport so it can be used later to send requests. + */ + protected abstract _setupTransport(): Transport | undefined; + + /** + * @inheritDoc + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + protected abstract _eventFromException(exception: any, hint?: EventHint): PromiseLike; + + /** + * @inheritDoc + */ + protected abstract _eventFromMessage(message: string, level?: SeverityLevel, hint?: EventHint): PromiseLike; } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 22308a0cb7e8..39f8319647f1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -25,10 +25,8 @@ export { getReportDialogEndpoint, } from './api'; export { BaseClient } from './baseclient'; -export { BackendClass, BaseBackend } from './basebackend'; export { eventToSentryRequest, sessionToSentryRequest } from './request'; export { initAndBind, ClientClass } from './sdk'; -export { NoopTransport } from './transports/noop'; export { SDK_VERSION } from './version'; import * as Integrations from './integrations'; diff --git a/packages/core/src/transports/noop.ts b/packages/core/src/transports/noop.ts deleted file mode 100644 index 0fc2c190f263..000000000000 --- a/packages/core/src/transports/noop.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Event, Response, Transport } from '@sentry/types'; -import { resolvedSyncPromise } from '@sentry/utils'; - -/** Noop transport */ -export class NoopTransport implements Transport { - /** - * @inheritDoc - */ - public sendEvent(_: Event): PromiseLike { - return resolvedSyncPromise({ - reason: `NoopTransport: Event has been skipped because no Dsn is configured.`, - status: 'skipped', - }); - } - - /** - * @inheritDoc - */ - public close(_?: number): PromiseLike { - return resolvedSyncPromise(true); - } -} diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index cd8d7f992fb4..886f22cd0f6e 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -3,7 +3,6 @@ import { Event, Span, Transport } from '@sentry/types'; import { logger, SentryError, SyncPromise } from '@sentry/utils'; import * as integrationModule from '../../src/integration'; -import { TestBackend } from '../mocks/backend'; import { TestClient } from '../mocks/client'; import { TestIntegration } from '../mocks/integration'; import { FakeTransport } from '../mocks/transport'; @@ -12,7 +11,8 @@ const PUBLIC_DSN = 'https://username@domain/123'; // eslint-disable-next-line no-var declare var global: any; -const backendEventFromException = jest.spyOn(TestBackend.prototype, 'eventFromException'); +// @ts-ignore inspecting protected method +const clientEventFromException = jest.spyOn(TestClient.prototype, '_eventFromException'); const clientProcess = jest.spyOn(TestClient.prototype as any, '_process'); jest.mock('@sentry/utils', () => { @@ -55,8 +55,8 @@ jest.mock('@sentry/utils', () => { describe('BaseClient', () => { beforeEach(() => { - TestBackend.sendEventCalled = undefined; - TestBackend.instance = undefined; + TestClient.sendEventCalled = undefined; + TestClient.instance = undefined; }); afterEach(() => { @@ -92,12 +92,12 @@ describe('BaseClient', () => { }); describe('getTransport()', () => { - test('returns the transport from backend', () => { + test('returns the transport from client', () => { expect.assertions(2); const options = { dsn: PUBLIC_DSN, transport: FakeTransport }; const client = new TestClient(options); expect(client.getTransport()).toBeInstanceOf(FakeTransport); - expect(TestBackend.instance!.getTransport()).toBe(client.getTransport()); + expect(TestClient.instance!.getTransport()).toBe(client.getTransport()); }); }); @@ -189,7 +189,7 @@ describe('BaseClient', () => { test('captures and sends exceptions', () => { const client = new TestClient({ dsn: PUBLIC_DSN }); client.captureException(new Error('test exception')); - expect(TestBackend.instance!.event).toEqual({ + expect(TestClient.instance!.event).toEqual({ environment: 'production', event_id: '42', exception: { @@ -219,7 +219,7 @@ describe('BaseClient', () => { }, scope, ); - expect(TestBackend.instance!.event).toEqual( + expect(TestClient.instance!.event).toEqual( expect.objectContaining({ extra: { bar: 'wat', @@ -244,7 +244,7 @@ describe('BaseClient', () => { }, scope, ); - expect(TestBackend.instance!.event).toEqual( + expect(TestClient.instance!.event).toEqual( expect.objectContaining({ extra: { bar: 'wat', @@ -269,12 +269,12 @@ describe('BaseClient', () => { client.captureException(thrown); expect(thrown.__sentry_captured__).toBe(true); - expect(backendEventFromException).toHaveBeenCalledTimes(1); + expect(clientEventFromException).toHaveBeenCalledTimes(1); client.captureException(thrown); // `captureException` should bail right away this second time around and not get as far as calling this again - expect(backendEventFromException).toHaveBeenCalledTimes(1); + expect(clientEventFromException).toHaveBeenCalledTimes(1); }); }); @@ -282,7 +282,7 @@ describe('BaseClient', () => { test('captures and sends messages', () => { const client = new TestClient({ dsn: PUBLIC_DSN }); client.captureMessage('test message'); - expect(TestBackend.instance!.event).toEqual({ + expect(TestClient.instance!.event).toEqual({ environment: 'production', event_id: '42', level: 'info', @@ -293,7 +293,8 @@ describe('BaseClient', () => { test('should call eventFromException if input to captureMessage is not a primitive', () => { const client = new TestClient({ dsn: PUBLIC_DSN }); - const spy = jest.spyOn(TestBackend.instance!, 'eventFromException'); + // @ts-ignore grabbing protected instance + const spy = jest.spyOn(TestClient.instance!, '_eventFromException'); client.captureMessage('foo'); client.captureMessage(null as any); @@ -323,7 +324,7 @@ describe('BaseClient', () => { }, scope, ); - expect(TestBackend.instance!.event).toEqual( + expect(TestClient.instance!.event).toEqual( expect.objectContaining({ extra: { bar: 'wat', @@ -341,7 +342,7 @@ describe('BaseClient', () => { const client = new TestClient({ enabled: false, dsn: PUBLIC_DSN }); const scope = new Scope(); client.captureEvent({}, undefined, scope); - expect(TestBackend.instance!.event).toBeUndefined(); + expect(TestClient.instance!.event).toBeUndefined(); }); test('skips without a Dsn', () => { @@ -349,7 +350,7 @@ describe('BaseClient', () => { const client = new TestClient({}); const scope = new Scope(); client.captureEvent({}, undefined, scope); - expect(TestBackend.instance!.event).toBeUndefined(); + expect(TestClient.instance!.event).toBeUndefined(); }); test.each([ @@ -386,8 +387,8 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN }); const scope = new Scope(); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!.message).toBe('message'); - expect(TestBackend.instance!.event).toEqual({ + expect(TestClient.instance!.event!.message).toBe('message'); + expect(TestClient.instance!.event).toEqual({ environment: 'production', event_id: '42', message: 'message', @@ -400,8 +401,8 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN }); const scope = new Scope(); client.captureEvent({ message: 'message', timestamp: 1234 }, undefined, scope); - expect(TestBackend.instance!.event!.message).toBe('message'); - expect(TestBackend.instance!.event).toEqual({ + expect(TestClient.instance!.event!.message).toBe('message'); + expect(TestClient.instance!.event).toEqual({ environment: 'production', event_id: '42', message: 'message', @@ -414,7 +415,7 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN }); const scope = new Scope(); client.captureEvent({ message: 'message' }, { event_id: 'wat' }, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: 'production', event_id: 'wat', message: 'message', @@ -429,7 +430,7 @@ describe('BaseClient', () => { }); const scope = new Scope(); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: 'production', event_id: '42', message: 'message', @@ -445,7 +446,7 @@ describe('BaseClient', () => { }); const scope = new Scope(); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: 'env', event_id: '42', message: 'message', @@ -461,7 +462,7 @@ describe('BaseClient', () => { }); const scope = new Scope(); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: undefined, event_id: '42', message: 'message', @@ -477,7 +478,7 @@ describe('BaseClient', () => { }); const scope = new Scope(); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: 'production', event_id: '42', message: 'message', @@ -492,10 +493,10 @@ describe('BaseClient', () => { const scope = new Scope(); scope.addBreadcrumb({ message: 'breadcrumb' }, 100); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toHaveProperty('event_id', '42'); - expect(TestBackend.instance!.event!).toHaveProperty('message', 'message'); - expect(TestBackend.instance!.event!).toHaveProperty('breadcrumbs'); - expect(TestBackend.instance!.event!.breadcrumbs![0]).toHaveProperty('message', 'breadcrumb'); + expect(TestClient.instance!.event!).toHaveProperty('event_id', '42'); + expect(TestClient.instance!.event!).toHaveProperty('message', 'message'); + expect(TestClient.instance!.event!).toHaveProperty('breadcrumbs'); + expect(TestClient.instance!.event!.breadcrumbs![0]).toHaveProperty('message', 'breadcrumb'); }); test('limits previously saved breadcrumbs', () => { @@ -506,8 +507,8 @@ describe('BaseClient', () => { hub.addBreadcrumb({ message: '1' }); hub.addBreadcrumb({ message: '2' }); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!.breadcrumbs).toHaveLength(1); - expect(TestBackend.instance!.event!.breadcrumbs![0].message).toEqual('2'); + expect(TestClient.instance!.event!.breadcrumbs).toHaveLength(1); + expect(TestClient.instance!.event!.breadcrumbs![0].message).toEqual('2'); }); test('adds context data', () => { @@ -518,7 +519,7 @@ describe('BaseClient', () => { scope.setTag('a', 'a'); scope.setUser({ id: 'user' }); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: 'production', event_id: '42', extra: { b: 'b' }, @@ -535,7 +536,7 @@ describe('BaseClient', () => { const scope = new Scope(); scope.setFingerprint(['abcd']); client.captureEvent({ message: 'message' }, undefined, scope); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ environment: 'production', event_id: '42', fingerprint: ['abcd'], @@ -548,7 +549,7 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN, integrations: [new TestIntegration()] }); client.setupIntegrations(); client.captureEvent({ message: 'message' }); - expect(TestBackend.instance!.event!.sdk).toEqual({ + expect(TestClient.instance!.event!.sdk).toEqual({ integrations: ['TestIntegration'], }); }); @@ -588,7 +589,7 @@ describe('BaseClient', () => { extra: fourLevelsObject, user: fourLevelsObject, }); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb], contexts: normalizedObject, environment: 'production', @@ -634,7 +635,7 @@ describe('BaseClient', () => { extra: fourLevelsObject, user: fourLevelsObject, }); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb], contexts: normalizedObject, environment: 'production', @@ -685,7 +686,7 @@ describe('BaseClient', () => { extra: fourLevelsObject, user: fourLevelsObject, }); - expect(TestBackend.instance!.event!).toEqual({ + expect(TestClient.instance!.event!).toEqual({ breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb], contexts: normalizedObject, environment: 'production', @@ -742,7 +743,7 @@ describe('BaseClient', () => { // input transaction. const normalizedTransaction = JSON.parse(JSON.stringify(transaction)); // deep-copy client.captureEvent(transaction); - expect(TestBackend.instance!.event!).toEqual(normalizedTransaction); + expect(TestClient.instance!.event!).toEqual(normalizedTransaction); }); test('calls beforeSend and uses original event without any changes', () => { @@ -750,7 +751,7 @@ describe('BaseClient', () => { const beforeSend = jest.fn(event => event); const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); client.captureEvent({ message: 'hello' }); - expect(TestBackend.instance!.event!.message).toBe('hello'); + expect(TestClient.instance!.event!.message).toBe('hello'); }); test('calls beforeSend and uses the new one', () => { @@ -758,7 +759,7 @@ describe('BaseClient', () => { const beforeSend = jest.fn(() => ({ message: 'changed1' })); const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); client.captureEvent({ message: 'hello' }); - expect(TestBackend.instance!.event!.message).toBe('changed1'); + expect(TestClient.instance!.event!.message).toBe('changed1'); }); test('calls beforeSend and discards the event', () => { @@ -768,7 +769,7 @@ describe('BaseClient', () => { const captureExceptionSpy = jest.spyOn(client, 'captureException'); const loggerErrorSpy = jest.spyOn(logger, 'error'); client.captureEvent({ message: 'hello' }); - expect(TestBackend.instance!.event).toBeUndefined(); + expect(TestClient.instance!.event).toBeUndefined(); expect(captureExceptionSpy).not.toBeCalled(); expect(loggerErrorSpy).toBeCalledWith(new SentryError('`beforeSend` returned `null`, will not send event.')); }); @@ -783,7 +784,7 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); const loggerErrorSpy = jest.spyOn(logger, 'error'); client.captureEvent({ message: 'hello' }); - expect(TestBackend.instance!.event).toBeUndefined(); + expect(TestClient.instance!.event).toBeUndefined(); expect(loggerErrorSpy).toBeCalledWith( new SentryError('`beforeSend` method has to return `null` or a valid event.'), ); @@ -804,7 +805,7 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); client.captureEvent({ message: 'hello' }); jest.runOnlyPendingTimers(); - TestBackend.sendEventCalled = (event: Event) => { + TestClient.sendEventCalled = (event: Event) => { expect(event.message).toBe('hello'); }; setTimeout(() => { @@ -828,7 +829,7 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); client.captureEvent({ message: 'hello' }); jest.runOnlyPendingTimers(); - TestBackend.sendEventCalled = (event: Event) => { + TestClient.sendEventCalled = (event: Event) => { expect(event.message).toBe('changed2'); }; setTimeout(() => { @@ -851,7 +852,7 @@ describe('BaseClient', () => { const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); client.captureEvent({ message: 'hello' }); jest.runAllTimers(); - expect(TestBackend.instance!.event).toBeUndefined(); + expect(TestClient.instance!.event).toBeUndefined(); }); test('beforeSend gets access to a hint as a second argument', () => { @@ -859,8 +860,8 @@ describe('BaseClient', () => { const beforeSend = jest.fn((event, hint) => ({ ...event, data: hint.data })); const client = new TestClient({ dsn: PUBLIC_DSN, beforeSend }); client.captureEvent({ message: 'hello' }, { data: 'someRandomThing' }); - expect(TestBackend.instance!.event!.message).toBe('hello'); - expect((TestBackend.instance!.event! as any).data).toBe('someRandomThing'); + expect(TestClient.instance!.event!.message).toBe('hello'); + expect((TestClient.instance!.event! as any).data).toBe('someRandomThing'); }); test('beforeSend records dropped events', () => { @@ -893,7 +894,7 @@ describe('BaseClient', () => { const scope = new Scope(); scope.addEventProcessor(() => null); client.captureEvent({ message: 'hello' }, {}, scope); - expect(TestBackend.instance!.event).toBeUndefined(); + expect(TestClient.instance!.event).toBeUndefined(); expect(captureExceptionSpy).not.toBeCalled(); expect(loggerErrorSpy).toBeCalledWith(new SentryError('An event processor returned null, will not send event.')); }); @@ -928,7 +929,7 @@ describe('BaseClient', () => { throw exception; }); client.captureEvent({ message: 'hello' }, {}, scope); - expect(TestBackend.instance!.event!.exception!.values![0]).toStrictEqual({ type: 'Error', value: 'sorry' }); + expect(TestClient.instance!.event!.exception!.values![0]).toStrictEqual({ type: 'Error', value: 'sorry' }); expect(captureExceptionSpy).toBeCalledWith(exception, { data: { __sentry__: true, @@ -1054,7 +1055,8 @@ describe('BaseClient', () => { }); const delay = 300; - const spy = jest.spyOn(TestBackend.instance!, 'eventFromMessage'); + // @ts-ignore grabbing protected instance + const spy = jest.spyOn(TestClient.instance!, '_eventFromMessage'); spy.mockImplementationOnce( (message, level) => new SyncPromise(resolve => { @@ -1115,12 +1117,12 @@ describe('BaseClient', () => { }); describe('captureSession()', () => { - test('sends sessions to the backend', () => { + test('sends sessions to the client', () => { expect.assertions(1); const client = new TestClient({ dsn: PUBLIC_DSN }); const session = new Session({ release: 'test' }); client.captureSession(session); - expect(TestBackend.instance!.session).toEqual(session); + expect(TestClient.instance!.session).toEqual(session); }); test('skips when disabled', () => { @@ -1128,7 +1130,7 @@ describe('BaseClient', () => { const client = new TestClient({ enabled: false, dsn: PUBLIC_DSN }); const session = new Session({ release: 'test' }); client.captureSession(session); - expect(TestBackend.instance!.session).toBeUndefined(); + expect(TestClient.instance!.session).toBeUndefined(); }); }); }); diff --git a/packages/core/test/mocks/backend.ts b/packages/core/test/mocks/backend.ts deleted file mode 100644 index 10e3862211f8..000000000000 --- a/packages/core/test/mocks/backend.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Session } from '@sentry/hub'; -import { Event, Options, SeverityLevel, Transport } from '@sentry/types'; -import { resolvedSyncPromise } from '@sentry/utils'; - -import { BaseBackend } from '../../src/basebackend'; - -export interface TestOptions extends Options { - test?: boolean; - mockInstallFailure?: boolean; - enableSend?: boolean; -} - -export class TestBackend extends BaseBackend { - public static instance?: TestBackend; - public static sendEventCalled?: (event: Event) => void; - - public event?: Event; - public session?: Session; - - public constructor(protected readonly _options: TestOptions) { - super(_options); - TestBackend.instance = this; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types - public eventFromException(exception: any): PromiseLike { - return resolvedSyncPromise({ - exception: { - values: [ - { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ - type: exception.name, - value: exception.message, - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ - }, - ], - }, - }); - } - - public eventFromMessage(message: string, level: SeverityLevel = 'info'): PromiseLike { - return resolvedSyncPromise({ message, level }); - } - - public sendEvent(event: Event): void { - this.event = event; - if (this._options.enableSend) { - super.sendEvent(event); - return; - } - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - TestBackend.sendEventCalled && TestBackend.sendEventCalled(event); - } - - public sendSession(session: Session): void { - this.session = session; - } - - protected _setupTransport(): Transport { - if (!this._options.dsn) { - // We return the noop transport here in case there is no Dsn. - return super._setupTransport(); - } - - const transportOptions = this._options.transportOptions - ? this._options.transportOptions - : { dsn: this._options.dsn }; - - if (this._options.transport) { - return new this._options.transport(transportOptions); - } - - return super._setupTransport(); - } -} diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 810328818eae..c896c2d92e01 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -1,14 +1,96 @@ +import { Event, EventHint, Options, Response, Session, SeverityLevel, Transport } from '@sentry/types'; +import { resolvedSyncPromise } from '@sentry/utils'; + import { BaseClient } from '../../src/baseclient'; import { initAndBind } from '../../src/sdk'; -import { TestBackend, TestOptions } from './backend'; -export class TestClient extends BaseClient { +class NoopTransport implements Transport { + /** + * @inheritDoc + */ + public sendEvent(_: Event): PromiseLike { + return resolvedSyncPromise({ + reason: `NoopTransport: Event has been skipped because no Dsn is configured.`, + status: 'skipped', + }); + } + + /** + * @inheritDoc + */ + public close(_?: number): PromiseLike { + return resolvedSyncPromise(true); + } +} + +interface TestOptions extends Options { + test?: boolean; + mockInstallFailure?: boolean; + enableSend?: boolean; +} + +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) { - super(TestBackend, options); + super(options); TestClient.instance = this; } + + public sendEvent(event: Event): void { + this.event = event; + if (this._options.enableSend) { + super.sendEvent(event); + return; + } + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + TestClient.sendEventCalled && TestClient.sendEventCalled(event); + } + + public sendSession(session: Session): void { + this.session = session; + } + + protected _setupTransport(): Transport { + if (!this._options.dsn) { + // We return the noop transport here in case there is no Dsn. + return new NoopTransport(); + } + + const transportOptions = this._options.transportOptions + ? this._options.transportOptions + : { dsn: this._options.dsn }; + + if (this._options.transport) { + return new this._options.transport(transportOptions); + } + + return new NoopTransport(); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + protected _eventFromException(exception: any, _hint?: EventHint): PromiseLike { + return resolvedSyncPromise({ + exception: { + values: [ + { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + type: exception.name, + value: exception.message, + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + }, + ], + }, + }); + } + + protected _eventFromMessage(message: string, level: SeverityLevel = 'info'): PromiseLike { + return resolvedSyncPromise({ message, level }); + } } export function init(options: TestOptions): void { diff --git a/packages/node/src/backend.ts b/packages/node/src/backend.ts deleted file mode 100644 index c18dd3aaafd9..000000000000 --- a/packages/node/src/backend.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { BaseBackend } from '@sentry/core'; -import { Event, EventHint, SeverityLevel, Transport, TransportOptions } from '@sentry/types'; -import { Dsn } from '@sentry/utils'; - -import { eventFromException, eventFromMessage } from './eventbuilder'; -import { HTTPSTransport, HTTPTransport } from './transports'; -import { NodeOptions } from './types'; - -/** - * The Sentry Node SDK Backend. - * @hidden - */ -export class NodeBackend extends BaseBackend { - /** - * @inheritDoc - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types - public eventFromException(exception: any, hint?: EventHint): PromiseLike { - return eventFromException(this._options, exception, hint); - } - - /** - * @inheritDoc - */ - public eventFromMessage(message: string, level: SeverityLevel = 'info', hint?: EventHint): PromiseLike { - return eventFromMessage(this._options, message, level, hint); - } - - /** - * @inheritDoc - */ - protected _setupTransport(): Transport { - if (!this._options.dsn) { - // We return the noop transport here in case there is no Dsn. - return super._setupTransport(); - } - - const dsn = new Dsn(this._options.dsn); - - const transportOptions: TransportOptions = { - ...this._options.transportOptions, - ...(this._options.httpProxy && { httpProxy: this._options.httpProxy }), - ...(this._options.httpsProxy && { httpsProxy: this._options.httpsProxy }), - ...(this._options.caCerts && { caCerts: this._options.caCerts }), - dsn: this._options.dsn, - tunnel: this._options.tunnel, - _metadata: this._options._metadata, - }; - - if (this._options.transport) { - return new this._options.transport(transportOptions); - } - if (dsn.protocol === 'http') { - return new HTTPTransport(transportOptions); - } - return new HTTPSTransport(transportOptions); - } -} diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index c5f19ed611d0..e00fd67bcf39 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -1,9 +1,10 @@ import { BaseClient, Scope, SDK_VERSION } from '@sentry/core'; import { SessionFlusher } from '@sentry/hub'; -import { Event, EventHint } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { Event, EventHint, Response, SeverityLevel, Transport, TransportOptions } from '@sentry/types'; +import { Dsn, logger, resolvedSyncPromise } from '@sentry/utils'; -import { NodeBackend } from './backend'; +import { eventFromException, eventFromMessage } from './eventbuilder'; +import { HTTPSTransport, HTTPTransport } from './transports'; import { NodeOptions } from './types'; /** @@ -12,7 +13,7 @@ import { NodeOptions } from './types'; * @see NodeOptions for documentation on configuration options. * @see SentryClient for usage documentation. */ -export class NodeClient extends BaseClient { +export class NodeClient extends BaseClient { protected _sessionFlusher: SessionFlusher | undefined; /** @@ -32,7 +33,7 @@ export class NodeClient extends BaseClient { version: SDK_VERSION, }; - super(NodeBackend, options); + super(options); } /** @@ -95,14 +96,22 @@ export class NodeClient extends BaseClient { /** Method that initialises an instance of SessionFlusher on Client */ public initSessionFlusher(): void { const { release, environment } = this._options; + const transport = this.getTransport(); + + if (!transport) { + logger.warn('Cannot initialise an instance of SessionFlusher if no transport is initialized!'); + return; + } + if (!release) { logger.warn('Cannot initialise an instance of SessionFlusher if no release is provided!'); - } else { - this._sessionFlusher = new SessionFlusher(this.getTransport(), { - release, - environment, - }); + return; } + + this._sessionFlusher = new SessionFlusher(transport, { + release, + environment, + }); } /** @@ -127,4 +136,67 @@ export class NodeClient extends BaseClient { this._sessionFlusher.incrementSessionStatusCount(); } } + + /** + * @inheritDoc + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + protected _eventFromException(exception: any, hint?: EventHint): PromiseLike { + return eventFromException(this._options, exception, hint); + } + + /** + * @inheritDoc + */ + protected _eventFromMessage(message: string, level: SeverityLevel = 'info', hint?: EventHint): PromiseLike { + return eventFromMessage(this._options, message, level, hint); + } + + /** + * @inheritDoc + */ + protected _setupTransport(): Transport { + if (!this._options.dsn) { + return new NoopTransport(); + } + + const dsn = new Dsn(this._options.dsn); + + const transportOptions: TransportOptions = { + ...this._options.transportOptions, + ...(this._options.httpProxy && { httpProxy: this._options.httpProxy }), + ...(this._options.httpsProxy && { httpsProxy: this._options.httpsProxy }), + ...(this._options.caCerts && { caCerts: this._options.caCerts }), + dsn: this._options.dsn, + tunnel: this._options.tunnel, + _metadata: this._options._metadata, + }; + + if (this._options.transport) { + return new this._options.transport(transportOptions); + } + if (dsn.protocol === 'http') { + return new HTTPTransport(transportOptions); + } + return new HTTPSTransport(transportOptions); + } +} + +class NoopTransport implements Transport { + /** + * @inheritDoc + */ + public sendEvent(_: Event): PromiseLike { + return resolvedSyncPromise({ + reason: `NoopTransport: Event has been skipped because no Dsn is configured.`, + status: 'skipped', + }); + } + + /** + * @inheritDoc + */ + public close(_?: number): PromiseLike { + return resolvedSyncPromise(true); + } } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 8aa44c75912b..6508fcfe803b 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -39,7 +39,6 @@ export { } from '@sentry/core'; export { NodeOptions } from './types'; -export { NodeBackend } from './backend'; export { NodeClient } from './client'; export { defaultIntegrations, init, lastEventId, flush, close, getSentryRelease } from './sdk'; export { deepReadDirSync } from './utils'; diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index daef81e4d44c..425fd9ffa424 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -2,11 +2,11 @@ import * as sentryCore from '@sentry/core'; import { Hub } from '@sentry/hub'; import * as sentryHub from '@sentry/hub'; import { Transaction } from '@sentry/tracing'; -import { Runtime } from '@sentry/types'; +import { Runtime, Transport, Response } from '@sentry/types'; import * as http from 'http'; import * as net from 'net'; -import { Event, Request, User } from '../src'; +import { Event, Request, User, Transports } from '../src'; import { NodeClient } from '../src/client'; import { errorHandler, diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index 126bc617ccb7..f52d997f9790 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -15,7 +15,6 @@ import { NodeClient, Scope, } from '../src'; -import { NodeBackend } from '../src/backend'; jest.mock('@sentry/core', () => { const original = jest.requireActual('@sentry/core'); @@ -77,7 +76,7 @@ describe('SentryNode', () => { let s: jest.SpyInstance; beforeEach(() => { - s = jest.spyOn(NodeBackend.prototype, 'sendEvent').mockImplementation(async () => Promise.resolve({ code: 200 })); + s = jest.spyOn(NodeClient.prototype, 'sendEvent').mockImplementation(async () => Promise.resolve({ code: 200 })); }); afterEach(() => { @@ -106,7 +105,7 @@ describe('SentryNode', () => { let s: jest.SpyInstance; beforeEach(() => { - s = jest.spyOn(NodeBackend.prototype, 'sendEvent').mockImplementation(async () => Promise.resolve({ code: 200 })); + s = jest.spyOn(NodeClient.prototype, 'sendEvent').mockImplementation(async () => Promise.resolve({ code: 200 })); }); afterEach(() => { @@ -320,7 +319,7 @@ describe('SentryNode initialization', () => { init({ dsn }); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const sdkData = (getCurrentHub().getClient() as any)._backend._transport._api.metadata?.sdk; + const sdkData = (getCurrentHub().getClient() as any)._transport._api.metadata?.sdk; expect(sdkData.name).toEqual('sentry.javascript.node'); expect(sdkData.packages[0].name).toEqual('npm:@sentry/node'); @@ -332,7 +331,7 @@ describe('SentryNode initialization', () => { const client = new NodeClient({ dsn }); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const sdkData = (client as any)._backend._transport._api.metadata?.sdk; + const sdkData = (client as any)._transport._api.metadata?.sdk; expect(sdkData.name).toEqual('sentry.javascript.node'); expect(sdkData.packages[0].name).toEqual('npm:@sentry/node'); @@ -361,7 +360,7 @@ describe('SentryNode initialization', () => { }); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const sdkData = (getCurrentHub().getClient() as any)._backend._transport._api.metadata?.sdk; + const sdkData = (getCurrentHub().getClient() as any)._transport._api.metadata?.sdk; expect(sdkData.name).toEqual('sentry.javascript.serverless'); expect(sdkData.packages[0].name).toEqual('npm:@sentry/serverless'); diff --git a/packages/node/test/integrations/linkederrors.test.ts b/packages/node/test/integrations/linkederrors.test.ts index 7dcaf077e4c6..c8b1d10b8f55 100644 --- a/packages/node/test/integrations/linkederrors.test.ts +++ b/packages/node/test/integrations/linkederrors.test.ts @@ -1,7 +1,7 @@ import { ExtendedError } from '@sentry/types'; +import { eventFromException } from '../../../browser/src'; import { Event } from '../../src'; -import { NodeBackend } from '../../src/backend'; import { LinkedErrors } from '../../src/integrations/linkederrors'; let linkedErrors: any; @@ -28,10 +28,8 @@ describe('LinkedErrors', () => { expect.assertions(2); const spy = jest.spyOn(linkedErrors, '_walkErrorTree'); const one = new Error('originalException'); - const backend = new NodeBackend({}); let event: Event | undefined; - return backend - .eventFromException(one) + return eventFromException({}, one) .then(eventFromException => { event = eventFromException; return linkedErrors._handler(eventFromException); @@ -51,8 +49,7 @@ describe('LinkedErrors', () => { }), ); const one = new Error('originalException'); - const backend = new NodeBackend({}); - return backend.eventFromException(one).then(event => + return eventFromException({}, one).then(event => linkedErrors ._handler(event, { originalException: one, @@ -71,8 +68,7 @@ describe('LinkedErrors', () => { one.cause = two; two.cause = three; - const backend = new NodeBackend({}); - return backend.eventFromException(one).then(event => + return eventFromException({}, one).then(event => linkedErrors ._handler(event, { originalException: one, @@ -104,8 +100,7 @@ describe('LinkedErrors', () => { one.reason = two; two.reason = three; - const backend = new NodeBackend({}); - return backend.eventFromException(one).then(event => + return eventFromException({}, one).then(event => linkedErrors ._handler(event, { originalException: one, @@ -137,8 +132,7 @@ describe('LinkedErrors', () => { one.cause = two; two.cause = three; - const backend = new NodeBackend({}); - return backend.eventFromException(one).then(event => + return eventFromException({}, one).then(event => linkedErrors ._handler(event, { originalException: one, diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index e71a3d2f39c6..ffc172aa4c6d 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -61,7 +61,7 @@ export interface Client { getOptions(): O; /** Returns clients transport. */ - getTransport?(): Transport; + getTransport?(): Transport | undefined; /** * Flush the event queue and set the client to `enabled = false`. See {@link Client.flush}.