Skip to content

Commit d64b798

Browse files
authored
feat(core): Add afterAllSetup hook for integrations (#10345)
This adds a new hook to integrations which runs after all integrations have run their `setup` hooks. This can be used to handle integrations that depend on each other. The only timing guarantee is that `afterAllSetup` will be called after every integration has run `setupOnce` and `setup`.
1 parent cbccf51 commit d64b798

File tree

4 files changed

+77
-2
lines changed

4 files changed

+77
-2
lines changed

packages/core/src/baseclient.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { createEventEnvelope, createSessionEnvelope } from './envelope';
5353
import { getClient } from './exports';
5454
import { getIsolationScope } from './hub';
5555
import type { IntegrationIndex } from './integration';
56+
import { afterSetupIntegrations } from './integration';
5657
import { setupIntegration, setupIntegrations } from './integration';
5758
import { createMetricEnvelope } from './metrics/envelope';
5859
import type { Scope } from './scope';
@@ -532,7 +533,10 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
532533

533534
/** Setup integrations for this client. */
534535
protected _setupIntegrations(): void {
535-
this._integrations = setupIntegrations(this, this._options.integrations);
536+
const { integrations } = this._options;
537+
this._integrations = setupIntegrations(this, integrations);
538+
afterSetupIntegrations(this, integrations);
539+
536540
// TODO v8: We don't need this flag anymore
537541
this._integrationsInitialized = true;
538542
}

packages/core/src/integration.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ export function setupIntegrations(client: Client, integrations: Integration[]):
108108
return integrationIndex;
109109
}
110110

111+
/**
112+
* Execute the `afterAllSetup` hooks of the given integrations.
113+
*/
114+
export function afterSetupIntegrations(client: Client, integrations: Integration[]): void {
115+
for (const integration of integrations) {
116+
// guard against empty provided integrations
117+
if (integration && integration.afterAllSetup) {
118+
integration.afterAllSetup(client);
119+
}
120+
}
121+
}
122+
111123
/** Setup a single integration. */
112124
export function setupIntegration(client: Client, integration: Integration, integrationIndex: IntegrationIndex): void {
113125
if (integrationIndex[integration.name]) {

packages/core/test/lib/sdk.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Hub, captureCheckIn, makeMain, setCurrentClient } from '@sentry/core';
2-
import type { Client, Integration } from '@sentry/types';
2+
import type { Client, Integration, IntegrationFnResult } from '@sentry/types';
33

44
import { installedIntegrations } from '../../src/integration';
55
import { initAndBind } from '../../src/sdk';
@@ -35,6 +35,53 @@ describe('SDK', () => {
3535
expect((integrations[0].setupOnce as jest.Mock).mock.calls.length).toBe(1);
3636
expect((integrations[1].setupOnce as jest.Mock).mock.calls.length).toBe(1);
3737
});
38+
39+
test('calls hooks in the correct order', () => {
40+
const list: string[] = [];
41+
42+
const integration1 = {
43+
name: 'integration1',
44+
setupOnce: jest.fn(() => list.push('setupOnce1')),
45+
afterAllSetup: jest.fn(() => list.push('afterAllSetup1')),
46+
} satisfies IntegrationFnResult;
47+
48+
const integration2 = {
49+
name: 'integration2',
50+
setupOnce: jest.fn(() => list.push('setupOnce2')),
51+
setup: jest.fn(() => list.push('setup2')),
52+
afterAllSetup: jest.fn(() => list.push('afterAllSetup2')),
53+
} satisfies IntegrationFnResult;
54+
55+
const integration3 = {
56+
name: 'integration3',
57+
setupOnce: jest.fn(() => list.push('setupOnce3')),
58+
setup: jest.fn(() => list.push('setup3')),
59+
} satisfies IntegrationFnResult;
60+
61+
const integrations: Integration[] = [integration1, integration2, integration3];
62+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, integrations });
63+
initAndBind(TestClient, options);
64+
65+
expect(integration1.setupOnce).toHaveBeenCalledTimes(1);
66+
expect(integration2.setupOnce).toHaveBeenCalledTimes(1);
67+
expect(integration3.setupOnce).toHaveBeenCalledTimes(1);
68+
69+
expect(integration2.setup).toHaveBeenCalledTimes(1);
70+
expect(integration3.setup).toHaveBeenCalledTimes(1);
71+
72+
expect(integration1.afterAllSetup).toHaveBeenCalledTimes(1);
73+
expect(integration2.afterAllSetup).toHaveBeenCalledTimes(1);
74+
75+
expect(list).toEqual([
76+
'setupOnce1',
77+
'setupOnce2',
78+
'setup2',
79+
'setupOnce3',
80+
'setup3',
81+
'afterAllSetup1',
82+
'afterAllSetup2',
83+
]);
84+
});
3885
});
3986
});
4087

packages/types/src/integration.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export interface IntegrationFnResult {
3939
*/
4040
setup?(client: Client): void;
4141

42+
/**
43+
* This hook is triggered after `setupOnce()` and `setup()` have been called for all integrations.
44+
* You can use it if it is important that all other integrations have been run before.
45+
*/
46+
afterAllSetup?(client: Client): void;
47+
4248
/**
4349
* An optional hook that allows to preprocess an event _before_ it is passed to all other event processors.
4450
*/
@@ -83,6 +89,12 @@ export interface Integration {
8389
*/
8490
setup?(client: Client): void;
8591

92+
/**
93+
* This hook is triggered after `setupOnce()` and `setup()` have been called for all integrations.
94+
* You can use it if it is important that all other integrations have been run before.
95+
*/
96+
afterAllSetup?(client: Client): void;
97+
8698
/**
8799
* An optional hook that allows to preprocess an event _before_ it is passed to all other event processors.
88100
*/

0 commit comments

Comments
 (0)