diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index d59d596e0b82..c7736e278c1c 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -53,6 +53,7 @@ import { createEventEnvelope, createSessionEnvelope } from './envelope'; import { getClient } from './exports'; import { getIsolationScope } from './hub'; import type { IntegrationIndex } from './integration'; +import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; import { createMetricEnvelope } from './metrics/envelope'; import type { Scope } from './scope'; @@ -532,7 +533,10 @@ export abstract class BaseClient implements Client { /** Setup integrations for this client. */ protected _setupIntegrations(): void { - this._integrations = setupIntegrations(this, this._options.integrations); + const { integrations } = this._options; + this._integrations = setupIntegrations(this, integrations); + afterSetupIntegrations(this, integrations); + // TODO v8: We don't need this flag anymore this._integrationsInitialized = true; } diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 5b126459390b..8a91fa10e303 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -108,6 +108,18 @@ export function setupIntegrations(client: Client, integrations: Integration[]): return integrationIndex; } +/** + * Execute the `afterAllSetup` hooks of the given integrations. + */ +export function afterSetupIntegrations(client: Client, integrations: Integration[]): void { + for (const integration of integrations) { + // guard against empty provided integrations + if (integration && integration.afterAllSetup) { + integration.afterAllSetup(client); + } + } +} + /** Setup a single integration. */ export function setupIntegration(client: Client, integration: Integration, integrationIndex: IntegrationIndex): void { if (integrationIndex[integration.name]) { diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index 1484971babf7..c9d18c02c78e 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -1,5 +1,5 @@ import { Hub, captureCheckIn, makeMain, setCurrentClient } from '@sentry/core'; -import type { Client, Integration } from '@sentry/types'; +import type { Client, Integration, IntegrationFnResult } from '@sentry/types'; import { installedIntegrations } from '../../src/integration'; import { initAndBind } from '../../src/sdk'; @@ -35,6 +35,53 @@ describe('SDK', () => { 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('calls hooks in the correct order', () => { + const list: string[] = []; + + const integration1 = { + name: 'integration1', + setupOnce: jest.fn(() => list.push('setupOnce1')), + afterAllSetup: jest.fn(() => list.push('afterAllSetup1')), + } satisfies IntegrationFnResult; + + const integration2 = { + name: 'integration2', + setupOnce: jest.fn(() => list.push('setupOnce2')), + setup: jest.fn(() => list.push('setup2')), + afterAllSetup: jest.fn(() => list.push('afterAllSetup2')), + } satisfies IntegrationFnResult; + + const integration3 = { + name: 'integration3', + setupOnce: jest.fn(() => list.push('setupOnce3')), + setup: jest.fn(() => list.push('setup3')), + } satisfies IntegrationFnResult; + + const integrations: Integration[] = [integration1, integration2, integration3]; + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations }); + initAndBind(TestClient, options); + + expect(integration1.setupOnce).toHaveBeenCalledTimes(1); + expect(integration2.setupOnce).toHaveBeenCalledTimes(1); + expect(integration3.setupOnce).toHaveBeenCalledTimes(1); + + expect(integration2.setup).toHaveBeenCalledTimes(1); + expect(integration3.setup).toHaveBeenCalledTimes(1); + + expect(integration1.afterAllSetup).toHaveBeenCalledTimes(1); + expect(integration2.afterAllSetup).toHaveBeenCalledTimes(1); + + expect(list).toEqual([ + 'setupOnce1', + 'setupOnce2', + 'setup2', + 'setupOnce3', + 'setup3', + 'afterAllSetup1', + 'afterAllSetup2', + ]); + }); }); }); diff --git a/packages/types/src/integration.ts b/packages/types/src/integration.ts index 44c49ab375aa..3c3b44eb0ed8 100644 --- a/packages/types/src/integration.ts +++ b/packages/types/src/integration.ts @@ -39,6 +39,12 @@ export interface IntegrationFnResult { */ setup?(client: Client): void; + /** + * This hook is triggered after `setupOnce()` and `setup()` have been called for all integrations. + * You can use it if it is important that all other integrations have been run before. + */ + afterAllSetup?(client: Client): void; + /** * An optional hook that allows to preprocess an event _before_ it is passed to all other event processors. */ @@ -83,6 +89,12 @@ export interface Integration { */ setup?(client: Client): void; + /** + * This hook is triggered after `setupOnce()` and `setup()` have been called for all integrations. + * You can use it if it is important that all other integrations have been run before. + */ + afterAllSetup?(client: Client): void; + /** * An optional hook that allows to preprocess an event _before_ it is passed to all other event processors. */