Skip to content

Commit ec9eebc

Browse files
authored
feat(core): Add addIntegration utility (#9186)
To make it easier to async add integrations later, which is useful e.g. for replay but also for other cases. Now you can just do: ```js Sentry.addIntegration(new Sentry.Replay()); ```
1 parent aeb4462 commit ec9eebc

File tree

11 files changed

+78
-2
lines changed

11 files changed

+78
-2
lines changed

packages/browser/src/exports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type { ReportDialogOptions } from './helpers';
2323
export {
2424
addGlobalEventProcessor,
2525
addBreadcrumb,
26+
addIntegration,
2627
captureException,
2728
captureEvent,
2829
captureMessage,

packages/bun/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type { BunOptions } from './types';
2626
export {
2727
addGlobalEventProcessor,
2828
addBreadcrumb,
29+
addIntegration,
2930
captureException,
3031
captureEvent,
3132
captureMessage,

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export { createTransport } from './transports/base';
4646
export { makeOfflineTransport } from './transports/offline';
4747
export { makeMultiplexedTransport } from './transports/multiplexed';
4848
export { SDK_VERSION } from './version';
49-
export { getIntegrationsToSetup } from './integration';
49+
export { getIntegrationsToSetup, addIntegration } from './integration';
5050
export { FunctionToString, InboundFilters } from './integrations';
5151
export { prepareEvent } from './utils/prepareEvent';
5252
export { createCheckInEnvelope } from './checkin';

packages/core/src/integration.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ export function setupIntegration(client: Client, integration: Integration, integ
124124
__DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`);
125125
}
126126

127+
/** Add an integration to the current hub's client. */
128+
export function addIntegration(integration: Integration): void {
129+
const client = getCurrentHub().getClient();
130+
131+
if (!client || !client.addIntegration) {
132+
__DEBUG_BUILD__ && logger.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`);
133+
return;
134+
}
135+
136+
client.addIntegration(integration);
137+
}
138+
127139
// Polyfill for Array.findIndex(), which is not supported in ES5
128140
function findIndex<T>(arr: T[], callback: (item: T) => boolean): number {
129141
for (let i = 0; i < arr.length; i++) {

packages/core/test/lib/base.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ describe('BaseClient', () => {
7878
});
7979

8080
test('handles being passed an invalid Dsn', () => {
81+
// Hide warning logs in the test
82+
jest.spyOn(console, 'error').mockImplementation(() => {});
83+
8184
const options = getDefaultTestClientOptions({ dsn: 'abc' });
8285
const client = new TestClient(options);
8386

packages/core/test/lib/integration.test.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Integration, Options } from '@sentry/types';
2+
import { logger } from '@sentry/utils';
23

3-
import { getIntegrationsToSetup, installedIntegrations, setupIntegration } from '../../src/integration';
4+
import { Hub, makeMain } from '../../src/hub';
5+
import { addIntegration, getIntegrationsToSetup, installedIntegrations, setupIntegration } from '../../src/integration';
46
import { getDefaultTestClientOptions, TestClient } from '../mocks/client';
57

68
function getTestClient(): TestClient {
@@ -559,3 +561,51 @@ describe('setupIntegration', () => {
559561
expect(sendEvent).not.toHaveBeenCalled();
560562
});
561563
});
564+
565+
describe('addIntegration', () => {
566+
beforeEach(function () {
567+
// Reset the (global!) list of installed integrations
568+
installedIntegrations.splice(0, installedIntegrations.length);
569+
});
570+
571+
afterEach(() => {
572+
jest.clearAllMocks();
573+
});
574+
575+
it('works with a client setup', () => {
576+
const warnings = jest.spyOn(logger, 'warn');
577+
578+
class CustomIntegration implements Integration {
579+
name = 'test';
580+
setupOnce = jest.fn();
581+
}
582+
583+
const client = getTestClient();
584+
const hub = new Hub(client);
585+
makeMain(hub);
586+
587+
const integration = new CustomIntegration();
588+
addIntegration(integration);
589+
590+
expect(integration.setupOnce).toHaveBeenCalledTimes(1);
591+
expect(warnings).not.toHaveBeenCalled();
592+
});
593+
594+
it('works without a client setup', () => {
595+
const warnings = jest.spyOn(logger, 'warn');
596+
class CustomIntegration implements Integration {
597+
name = 'test';
598+
setupOnce = jest.fn();
599+
}
600+
601+
const hub = new Hub();
602+
makeMain(hub);
603+
604+
const integration = new CustomIntegration();
605+
addIntegration(integration);
606+
607+
expect(integration.setupOnce).not.toHaveBeenCalled();
608+
expect(warnings).toHaveBeenCalledTimes(1);
609+
expect(warnings).toHaveBeenCalledWith('Cannot add integration "test" because no SDK Client is available.');
610+
});
611+
});

packages/core/test/lib/transports/multiplexed.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ describe('makeMultiplexedTransport', () => {
8888
});
8989

9090
it('Falls back to options DSN when a matched DSN is invalid', async () => {
91+
// Hide warning logs in the test
92+
jest.spyOn(console, 'error').mockImplementation(() => {});
93+
9194
expect.assertions(1);
9295

9396
const makeTransport = makeMultiplexedTransport(
@@ -99,6 +102,8 @@ describe('makeMultiplexedTransport', () => {
99102

100103
const transport = makeTransport({ url: DSN1_URL, ...transportOptions });
101104
await transport.send(ERROR_ENVELOPE);
105+
106+
jest.clearAllMocks();
102107
});
103108

104109
it('DSN can be overridden via match callback', async () => {

packages/node/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type { NodeOptions } from './types';
2626
export {
2727
addGlobalEventProcessor,
2828
addBreadcrumb,
29+
addIntegration,
2930
captureException,
3031
captureEvent,
3132
captureMessage,

packages/serverless/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export {
1515
Scope,
1616
addBreadcrumb,
1717
addGlobalEventProcessor,
18+
addIntegration,
1819
autoDiscoverNodePerformanceMonitoringIntegrations,
1920
captureEvent,
2021
captureException,

packages/sveltekit/src/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
export {
77
addGlobalEventProcessor,
88
addBreadcrumb,
9+
addIntegration,
910
captureException,
1011
captureEvent,
1112
captureMessage,

packages/vercel-edge/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type { VercelEdgeOptions } from './types';
2525
export {
2626
addGlobalEventProcessor,
2727
addBreadcrumb,
28+
addIntegration,
2829
captureException,
2930
captureEvent,
3031
captureMessage,

0 commit comments

Comments
 (0)