Skip to content

Commit 5498a78

Browse files
authored
feat(core): Add client.getIntegrationByName() (#10130)
And also deprecate both `getIntegration()` as well as `getIntegrationById()` which is only on the baseclient, but not on the client type, anyhow :grimace:. Usage: ```ts const replay = getClient().getIntegrationByName('Replay'); ``` Or, if you want to have an easier time with types: ```ts const replay = getClient().getIntegrationByName<Replay>('Replay'); ``` ## Why do we need this In v8, integrations will not be classes anymore, so you cannot pass a class definition anymore to `getIntegration()`. We also decided to remove the static `id` field, so you cannot find an integration via that way anymore. `getIntegrationById()` was always just on the baseclient, so not properly types anyhow. It is also an inconsistent/weird naming because we will not have an `id` anymore for integrations at all.
1 parent dd72e4d commit 5498a78

File tree

25 files changed

+86
-54
lines changed

25 files changed

+86
-54
lines changed

MIGRATION.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ but in v8 you will not be able to rely on this always existing anymore - any int
1919
This should not affect most people, but in the case that you are manually calling `integration.setupOnce()` right now,
2020
make sure to guard it's existence properly.
2121

22+
## Deprecate `getIntegration()` and `getIntegrationById()`
23+
24+
This deprecates `getIntegration()` on both the hub & the client, as well as `getIntegrationById()` on the baseclient.
25+
Instead, use `getIntegrationByName()`. You can optionally pass an integration generic to make it easier to work with
26+
typescript:
27+
28+
```ts
29+
const replay = getClient().getIntegrationByName<Replay>('Replay');
30+
```
31+
2232
## Deprecate `Hub`
2333

2434
The `Hub` has been a very important part of the Sentry SDK API up until now. Hubs were the SDK's "unit of concurrency"

dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customBrowserTracing/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ sentryTest('should handle custom added BrowserTracing integration', async ({ get
2727
expect(eventData.transaction_info?.source).toEqual('url');
2828

2929
const tracePropagationTargets = await page.evaluate(() => {
30-
const browserTracing = (window as any).Sentry.getClient().getIntegrationById('BrowserTracing');
30+
const browserTracing = (window as any).Sentry.getClient().getIntegrationByName('BrowserTracing');
3131
return browserTracing.options.tracePropagationTargets;
3232
});
3333

dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrations/test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ sentryTest('should handle custom added integrations & default integrations', asy
2424
});
2525

2626
const hasCustomIntegration = await page.evaluate(() => {
27-
return !!(window as any).Sentry.getClient().getIntegrationById('CustomIntegration');
27+
return !!(window as any).Sentry.getClient().getIntegrationByName('CustomIntegration');
2828
});
2929

3030
const hasReplay = await page.evaluate(() => {
31-
return !!(window as any).Sentry.getClient().getIntegrationById('Replay');
31+
return !!(window as any).Sentry.getClient().getIntegrationByName('Replay');
3232
});
3333
const hasBrowserTracing = await page.evaluate(() => {
34-
return !!(window as any).Sentry.getClient().getIntegrationById('BrowserTracing');
34+
return !!(window as any).Sentry.getClient().getIntegrationByName('BrowserTracing');
3535
});
3636

3737
expect(hasCustomIntegration).toEqual(true);

dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrationsFunction/test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ sentryTest(
2121
});
2222

2323
const hasCustomIntegration = await page.evaluate(() => {
24-
return !!(window as any).Sentry.getClient().getIntegrationById('CustomIntegration');
24+
return !!(window as any).Sentry.getClient().getIntegrationByName('CustomIntegration');
2525
});
2626

2727
const hasReplay = await page.evaluate(() => {
28-
return !!(window as any).Sentry.getClient().getIntegrationById('Replay');
28+
return !!(window as any).Sentry.getClient().getIntegrationByName('Replay');
2929
});
3030
const hasBrowserTracing = await page.evaluate(() => {
31-
return !!(window as any).Sentry.getClient().getIntegrationById('BrowserTracing');
31+
return !!(window as any).Sentry.getClient().getIntegrationByName('BrowserTracing');
3232
});
3333

3434
expect(hasCustomIntegration).toEqual(true);

dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customReplay/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ sentryTest('should handle custom added Replay integration', async ({ getLocalTes
2727
expect(eventData.segment_id).toBe(0);
2828

2929
const useCompression = await page.evaluate(() => {
30-
const replay = (window as any).Sentry.getClient().getIntegrationById('Replay');
30+
const replay = (window as any).Sentry.getClient().getIntegrationByName('Replay');
3131
return replay._replay.getOptions().useCompression;
3232
});
3333

packages/astro/test/client/sdk.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('Sentry client SDK', () => {
6161
});
6262

6363
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
64-
const browserTracing = getClient<BrowserClient>()?.getIntegrationById('BrowserTracing');
64+
const browserTracing = getClient<BrowserClient>()?.getIntegrationByName('BrowserTracing');
6565

6666
expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
6767
expect(browserTracing).toBeDefined();
@@ -77,7 +77,7 @@ describe('Sentry client SDK', () => {
7777
});
7878

7979
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
80-
const browserTracing = getClient<BrowserClient>()?.getIntegrationById('BrowserTracing');
80+
const browserTracing = getClient<BrowserClient>()?.getIntegrationByName('BrowserTracing');
8181

8282
expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
8383
expect(browserTracing).toBeUndefined();
@@ -92,7 +92,7 @@ describe('Sentry client SDK', () => {
9292
});
9393

9494
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
95-
const browserTracing = getClient<BrowserClient>()?.getIntegrationById('BrowserTracing');
95+
const browserTracing = getClient<BrowserClient>()?.getIntegrationByName('BrowserTracing');
9696

9797
expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
9898
expect(browserTracing).toBeUndefined();
@@ -109,7 +109,7 @@ describe('Sentry client SDK', () => {
109109

110110
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
111111

112-
const browserTracing = getClient<BrowserClient>()?.getIntegrationById('BrowserTracing') as BrowserTracing;
112+
const browserTracing = getClient<BrowserClient>()?.getIntegrationByName('BrowserTracing') as BrowserTracing;
113113
const options = browserTracing.options;
114114

115115
expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));

packages/core/src/baseclient.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,24 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
334334
* Gets an installed integration by its `id`.
335335
*
336336
* @returns The installed integration or `undefined` if no integration with that `id` was installed.
337+
* @deprecated Use `getIntegrationByName()` instead.
337338
*/
338339
public getIntegrationById(integrationId: string): Integration | undefined {
339-
return this._integrations[integrationId];
340+
return this.getIntegrationByName(integrationId);
340341
}
341342

342343
/**
343-
* @inheritDoc
344+
* Gets an installed integration by its name.
345+
*
346+
* @returns The installed integration or `undefined` if no integration with that `name` was installed.
347+
*/
348+
public getIntegrationByName<T extends Integration = Integration>(integrationName: string): T | undefined {
349+
return this._integrations[integrationName] as T | undefined;
350+
}
351+
352+
/**
353+
* Returns the client's instance of the given integration class, it any.
354+
* @deprecated Use `getIntegrationByName()` instead.
344355
*/
345356
public getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null {
346357
try {

packages/core/src/hub.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,14 @@ export class Hub implements HubInterface {
472472

473473
/**
474474
* @inheritDoc
475-
* @deprecated Use `Sentry.getClient().getIntegration()` instead.
475+
* @deprecated Use `Sentry.getClient().getIntegrationByName()` instead.
476476
*/
477477
public getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null {
478478
// eslint-disable-next-line deprecation/deprecation
479479
const client = this.getClient();
480480
if (!client) return null;
481481
try {
482+
// eslint-disable-next-line deprecation/deprecation
482483
return client.getIntegration(integration);
483484
} catch (_oO) {
484485
DEBUG_BUILD && logger.warn(`Cannot retrieve integration ${integration.id} from the current Hub`);

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,7 @@ describe('BaseClient', () => {
14961496
client.setupIntegrations();
14971497

14981498
expect(Object.keys((client as any)._integrations).length).toEqual(1);
1499-
expect(client.getIntegration(TestIntegration)).toBeTruthy();
1499+
expect(client.getIntegrationByName(TestIntegration.id)).toBeTruthy();
15001500
});
15011501

15021502
test('sets up each integration on `init` call', () => {
@@ -1507,7 +1507,7 @@ describe('BaseClient', () => {
15071507
client.init();
15081508

15091509
expect(Object.keys((client as any)._integrations).length).toEqual(1);
1510-
expect(client.getIntegration(TestIntegration)).toBeTruthy();
1510+
expect(client.getIntegrationByName(TestIntegration.id)).toBeTruthy();
15111511
});
15121512

15131513
test('skips installation for `setupIntegrations()` if DSN is not provided', () => {
@@ -1519,7 +1519,7 @@ describe('BaseClient', () => {
15191519
client.setupIntegrations();
15201520

15211521
expect(Object.keys((client as any)._integrations).length).toEqual(0);
1522-
expect(client.getIntegration(TestIntegration)).toBeFalsy();
1522+
expect(client.getIntegrationByName(TestIntegration.id)).toBeFalsy();
15231523
});
15241524

15251525
test('skips installation for `init()` if DSN is not provided', () => {
@@ -1530,7 +1530,7 @@ describe('BaseClient', () => {
15301530
client.init();
15311531

15321532
expect(Object.keys((client as any)._integrations).length).toEqual(0);
1533-
expect(client.getIntegration(TestIntegration)).toBeFalsy();
1533+
expect(client.getIntegrationByName(TestIntegration.id)).toBeFalsy();
15341534
});
15351535

15361536
test('skips installation for `setupIntegrations()` if `enabled` is set to `false`', () => {
@@ -1546,7 +1546,7 @@ describe('BaseClient', () => {
15461546
client.setupIntegrations();
15471547

15481548
expect(Object.keys((client as any)._integrations).length).toEqual(0);
1549-
expect(client.getIntegration(TestIntegration)).toBeFalsy();
1549+
expect(client.getIntegrationByName(TestIntegration.id)).toBeFalsy();
15501550
});
15511551

15521552
test('skips installation for `init()` if `enabled` is set to `false`', () => {
@@ -1561,7 +1561,7 @@ describe('BaseClient', () => {
15611561
client.init();
15621562

15631563
expect(Object.keys((client as any)._integrations).length).toEqual(0);
1564-
expect(client.getIntegration(TestIntegration)).toBeFalsy();
1564+
expect(client.getIntegrationByName(TestIntegration.id)).toBeFalsy();
15651565
});
15661566

15671567
test('skips installation if integrations are already installed', () => {
@@ -1577,7 +1577,7 @@ describe('BaseClient', () => {
15771577
client.setupIntegrations();
15781578

15791579
expect(Object.keys((client as any)._integrations).length).toEqual(1);
1580-
expect(client.getIntegration(TestIntegration)).toBeTruthy();
1580+
expect(client.getIntegrationByName(TestIntegration.id)).toBeTruthy();
15811581
expect(setupIntegrationsHelper).toHaveBeenCalledTimes(1);
15821582

15831583
// ...but it shouldn't try to install a second time
@@ -1597,7 +1597,7 @@ describe('BaseClient', () => {
15971597
client.init();
15981598

15991599
expect(Object.keys((client as any)._integrations).length).toEqual(1);
1600-
expect(client.getIntegration(TestIntegration)).toBeTruthy();
1600+
expect(client.getIntegrationByName(TestIntegration.id)).toBeTruthy();
16011601
expect(setupIntegrationsHelper).toHaveBeenCalledTimes(1);
16021602

16031603
client.init();

packages/core/test/mocks/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class TestIntegration implements Integration {
99

1010
public setupOnce(): void {
1111
const eventProcessor: EventProcessor = (event: Event) => {
12-
if (!getClient()?.getIntegration(TestIntegration)) {
12+
if (!getClient()?.getIntegrationByName?.('TestIntegration')) {
1313
return event;
1414
}
1515

packages/ember/addon/instance-initializers/sentry-performance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,8 @@ export async function instrumentForPerformance(appInstance: ApplicationInstance)
441441

442442
if (
443443
client &&
444-
(client as BrowserClient).getIntegrationById &&
445-
(client as BrowserClient).getIntegrationById('BrowserTracing')
444+
(client as BrowserClient).getIntegrationByName &&
445+
(client as BrowserClient).getIntegrationByName('BrowserTracing')
446446
) {
447447
// Initializers are called more than once in tests, causing the integrations to not be setup correctly.
448448
return;

packages/ember/tests/acceptance/sentry-replay-test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { visit } from '@ember/test-helpers';
22
import * as Sentry from '@sentry/ember';
3-
import type { ReplayContainer } from '@sentry/replay/build/npm/types/types';
3+
import type { BrowserClient, Replay } from '@sentry/ember';
44
import { setupApplicationTest } from 'ember-qunit';
55
import { module, test } from 'qunit';
66

@@ -13,10 +13,10 @@ module('Acceptance | Sentry Session Replay', function (hooks) {
1313
test('Test replay', async function (assert) {
1414
await visit('/replay');
1515

16-
const integration = Sentry.getClient()?.getIntegration(Sentry.Replay);
16+
const integration = Sentry.getClient<BrowserClient>()?.getIntegrationByName('Replay');
1717
assert.ok(integration);
1818

19-
const replay = (integration as Sentry.Replay)['_replay'] as ReplayContainer;
19+
const replay = (integration as Sentry.Replay)['_replay'] as Replay['_replay'];
2020

2121
assert.true(replay.isEnabled());
2222
assert.false(replay.isPaused());

packages/ember/tests/dummy/app/routes/replay.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import * as Sentry from '@sentry/ember';
55
export default class ReplayRoute extends Route {
66
public async beforeModel(): Promise<void> {
77
const { Replay } = Sentry;
8-
9-
if (!Sentry.getClient()!.getIntegration(Replay)) {
10-
const client = Sentry.getClient() as BrowserClient;
8+
const client = Sentry.getClient<BrowserClient>();
9+
if (client && !client.getIntegrationByName('Replay')) {
1110
client.addIntegration(new Replay());
1211
}
1312
}

packages/feedback/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ import {Feedback} from '@sentry-internal/feedback';
212212

213213
function MyFeedbackButton() {
214214
const client = getCurrentHub().getClient<BrowserClient>();
215-
const feedback = client?.getIntegration(Feedback);
215+
const feedback = client?.getIntegrationByName('Feedback');
216216

217217
// Don't render custom feedback button if Feedback integration not installed
218218
if (!feedback) {
@@ -258,7 +258,7 @@ import {BrowserClient, getCurrentHub} from '@sentry/react';
258258
import {Feedback} from '@sentry-internal/feedback';
259259

260260
document.getElementById('my-feedback-form').addEventListener('submit', (event) => {
261-
const feedback = getCurrentHub().getClient<BrowserClient>()?.getIntegration(Feedback);
261+
const feedback = getCurrentHub().getClient<BrowserClient>()?.getIntegrationByName('Feedback');
262262
const formData = new FormData(event.currentTarget);
263263
feedback.sendFeedback(formData);
264264
event.preventDefault();

packages/integrations/src/offline.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export class Offline implements Integration {
8181
}
8282

8383
const eventProcessor: EventProcessor = event => {
84+
// eslint-disable-next-line deprecation/deprecation
8485
if (this.hub && this.hub.getIntegration(Offline)) {
8586
// cache if we are positively offline
8687
if ('navigator' in WINDOW && 'onLine' in WINDOW.navigator && !WINDOW.navigator.onLine) {

packages/node-experimental/src/sdk/hub.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export function getCurrentHub(): Hub {
9494
},
9595

9696
getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null {
97+
// eslint-disable-next-line deprecation/deprecation
9798
return getClient().getIntegration(integration);
9899
},
99100

packages/node-experimental/src/sdk/spanProcessor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type { Span } from '@opentelemetry/sdk-trace-base';
44
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
55
import { SentrySpanProcessor, getClient, getSpanFinishScope } from '@sentry/opentelemetry';
66

7-
import { Http } from '../integrations/http';
8-
import { NodeFetch } from '../integrations/node-fetch';
7+
import type { Http } from '../integrations/http';
8+
import type { NodeFetch } from '../integrations/node-fetch';
99
import type { NodeExperimentalClient } from '../types';
1010
import { getIsolationScope } from './api';
1111
import { Scope } from './scope';
@@ -33,8 +33,8 @@ export class NodeExperimentalSentrySpanProcessor extends SentrySpanProcessor {
3333
/** @inheritDoc */
3434
protected _shouldSendSpanToSentry(span: Span): boolean {
3535
const client = getClient<NodeExperimentalClient>();
36-
const httpIntegration = client ? client.getIntegration(Http) : undefined;
37-
const fetchIntegration = client ? client.getIntegration(NodeFetch) : undefined;
36+
const httpIntegration = client ? client.getIntegrationByName<Http>('Http') : undefined;
37+
const fetchIntegration = client ? client.getIntegrationByName<NodeFetch>('NodeFetch') : undefined;
3838

3939
// If we encounter a client or server span with url & method, we assume this comes from the http instrumentation
4040
// In this case, if `shouldCreateSpansForRequests` is false, we want to _record_ the span but not _sample_ it,

packages/node-experimental/test/integration/transactions.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ describe('Integration | Transactions', () => {
512512

513513
const client = getClient() as NodeExperimentalClient;
514514

515-
jest.spyOn(client, 'getIntegration').mockImplementation(integrationClass => {
516-
if (integrationClass.name === 'Http') {
515+
jest.spyOn(client, 'getIntegrationByName').mockImplementation(name => {
516+
if (name === 'Http') {
517517
return {
518518
shouldCreateSpansForRequests: false,
519519
} as Http;
@@ -576,8 +576,8 @@ describe('Integration | Transactions', () => {
576576

577577
const client = getClient() as NodeExperimentalClient;
578578

579-
jest.spyOn(client, 'getIntegration').mockImplementation(integrationClass => {
580-
if (integrationClass.name === 'NodeFetch') {
579+
jest.spyOn(client, 'getIntegrationByName').mockImplementation(name => {
580+
if (name === 'NodeFetch') {
581581
return {
582582
shouldCreateSpansForRequests: false,
583583
} as NodeFetch;

packages/node/src/integrations/undici/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export class Undici implements Integration {
137137
}
138138

139139
private _onRequestCreate = (message: unknown): void => {
140+
// eslint-disable-next-line deprecation/deprecation
140141
if (!getClient()?.getIntegration(Undici)) {
141142
return;
142143
}
@@ -195,6 +196,7 @@ export class Undici implements Integration {
195196
};
196197

197198
private _onRequestEnd = (message: unknown): void => {
199+
// eslint-disable-next-line deprecation/deprecation
198200
if (!getClient()?.getIntegration(Undici)) {
199201
return;
200202
}
@@ -234,6 +236,7 @@ export class Undici implements Integration {
234236
};
235237

236238
private _onRequestError = (message: unknown): void => {
239+
// eslint-disable-next-line deprecation/deprecation
237240
if (!getClient()?.getIntegration(Undici)) {
238241
return;
239242
}

packages/node/test/integrations/undici.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ function setupTestServer() {
417417

418418
function patchUndici(userOptions: Partial<UndiciOptions>): () => void {
419419
try {
420-
const undici = getClient()!.getIntegration(Undici);
420+
const undici = getClient()!.getIntegrationByName!('Undici');
421421
// @ts-expect-error need to access private property
422422
options = { ...undici._options };
423423
// @ts-expect-error need to access private property
@@ -428,7 +428,7 @@ function patchUndici(userOptions: Partial<UndiciOptions>): () => void {
428428

429429
return () => {
430430
try {
431-
const undici = getClient()!.getIntegration(Undici);
431+
const undici = getClient()!.getIntegrationByName!('Undici');
432432
// @ts-expect-error Need to override readonly property
433433
undici!['_options'] = { ...options };
434434
} catch (_) {

0 commit comments

Comments
 (0)