Skip to content

Commit 1413568

Browse files
authored
ref(integrations): Rewrite pluggable integrations to use functional style (#9936)
Slowly getting there...
1 parent 213bb4a commit 1413568

21 files changed

+731
-911
lines changed

packages/core/src/integration.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,24 +175,28 @@ function findIndex<T>(arr: T[], callback: (item: T) => boolean): number {
175175
export function convertIntegrationFnToClass<Fn extends IntegrationFn>(
176176
name: string,
177177
fn: Fn,
178-
): IntegrationClass<
179-
Integration & {
180-
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
181-
}
182-
> {
178+
): {
179+
id: string;
180+
new (...args: Parameters<Fn>): Integration &
181+
ReturnType<Fn> & {
182+
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
183+
};
184+
} {
183185
return Object.assign(
184186
// eslint-disable-next-line @typescript-eslint/no-explicit-any
185-
function ConvertedIntegration(...rest: any[]) {
187+
function ConvertedIntegration(...rest: Parameters<Fn>) {
186188
return {
187189
// eslint-disable-next-line @typescript-eslint/no-empty-function
188190
setupOnce: () => {},
189191
...fn(...rest),
190192
};
191193
},
192194
{ id: name },
193-
) as unknown as IntegrationClass<
194-
Integration & {
195-
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
196-
}
197-
>;
195+
) as unknown as {
196+
id: string;
197+
new (...args: Parameters<Fn>): Integration &
198+
ReturnType<Fn> & {
199+
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
200+
};
201+
};
198202
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,23 @@ describe('convertIntegrationFnToClass', () => {
670670
});
671671
});
672672

673+
it('works with options', () => {
674+
const integrationFn = (_options: { num: number }) => ({ name: 'testName' });
675+
676+
const IntegrationClass = convertIntegrationFnToClass('testName', integrationFn);
677+
678+
expect(IntegrationClass.id).toBe('testName');
679+
680+
// @ts-expect-error This should fail TS without options
681+
new IntegrationClass();
682+
683+
const integration = new IntegrationClass({ num: 3 });
684+
expect(integration).toEqual({
685+
name: 'testName',
686+
setupOnce: expect.any(Function),
687+
});
688+
});
689+
673690
it('works with integration hooks', () => {
674691
const setup = jest.fn();
675692
const setupOnce = jest.fn();

packages/deno/src/integrations/deno-cron.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type CronParams = [string, string | Deno.CronSchedule, CronFn | CronOptions, Cro
99

1010
const INTEGRATION_NAME = 'DenoCron';
1111

12-
const SETUP_CLIENTS: Client[] = [];
12+
const SETUP_CLIENTS = new WeakMap<Client, boolean>();
1313

1414
const denoCronIntegration = (() => {
1515
return {
@@ -37,7 +37,7 @@ const denoCronIntegration = (() => {
3737
}
3838

3939
async function cronCalled(): Promise<void> {
40-
if (SETUP_CLIENTS.includes(getClient() as Client)) {
40+
if (SETUP_CLIENTS.has(getClient() as Client)) {
4141
return;
4242
}
4343

@@ -55,7 +55,7 @@ const denoCronIntegration = (() => {
5555
});
5656
},
5757
setup(client) {
58-
SETUP_CLIENTS.push(client);
58+
SETUP_CLIENTS.set(client, true);
5959
},
6060
};
6161
}) satisfies IntegrationFn;

packages/integrations/src/captureconsole.ts

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { captureException, captureMessage, getClient, withScope } from '@sentry/core';
2-
import type { CaptureContext, Client, EventProcessor, Hub, Integration } from '@sentry/types';
1+
import { captureException, captureMessage, convertIntegrationFnToClass, getClient, withScope } from '@sentry/core';
2+
import type { CaptureContext, IntegrationFn } from '@sentry/types';
33
import {
44
CONSOLE_LEVELS,
55
GLOBAL_OBJ,
@@ -9,55 +9,36 @@ import {
99
severityLevelFromString,
1010
} from '@sentry/utils';
1111

12-
/** Send Console API calls as Sentry Events */
13-
export class CaptureConsole implements Integration {
14-
/**
15-
* @inheritDoc
16-
*/
17-
public static id: string = 'CaptureConsole';
18-
19-
/**
20-
* @inheritDoc
21-
*/
22-
public name: string;
23-
24-
/**
25-
* @inheritDoc
26-
*/
27-
private readonly _levels: readonly string[];
28-
29-
/**
30-
* @inheritDoc
31-
*/
32-
public constructor(options: { levels?: string[] } = {}) {
33-
this.name = CaptureConsole.id;
34-
this._levels = options.levels || CONSOLE_LEVELS;
35-
}
36-
37-
/**
38-
* @inheritDoc
39-
*/
40-
public setupOnce(_: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void {
41-
// noop
42-
}
12+
interface CaptureConsoleOptions {
13+
levels?: string[];
14+
}
4315

44-
/** @inheritdoc */
45-
public setup(client: Client): void {
46-
if (!('console' in GLOBAL_OBJ)) {
47-
return;
48-
}
16+
const INTEGRATION_NAME = 'CaptureConsole';
4917

50-
const levels = this._levels;
18+
const captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => {
19+
const levels = options.levels || CONSOLE_LEVELS;
5120

52-
addConsoleInstrumentationHandler(({ args, level }) => {
53-
if (getClient() !== client || !levels.includes(level)) {
21+
return {
22+
name: INTEGRATION_NAME,
23+
setup(client) {
24+
if (!('console' in GLOBAL_OBJ)) {
5425
return;
5526
}
5627

57-
consoleHandler(args, level);
58-
});
59-
}
60-
}
28+
addConsoleInstrumentationHandler(({ args, level }) => {
29+
if (getClient() !== client || !levels.includes(level)) {
30+
return;
31+
}
32+
33+
consoleHandler(args, level);
34+
});
35+
},
36+
};
37+
}) satisfies IntegrationFn;
38+
39+
/** Send Console API calls as Sentry Events */
40+
// eslint-disable-next-line deprecation/deprecation
41+
export const CaptureConsole = convertIntegrationFnToClass(INTEGRATION_NAME, captureConsoleIntegration);
6142

6243
function consoleHandler(args: unknown[], level: string): void {
6344
const captureContext: CaptureContext = {

packages/integrations/src/contextlines.ts

Lines changed: 40 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import type { Event, Integration, StackFrame } from '@sentry/types';
1+
import { convertIntegrationFnToClass } from '@sentry/core';
2+
import type { Event, IntegrationFn, StackFrame } from '@sentry/types';
23
import { GLOBAL_OBJ, addContextToFrame, stripUrlQueryAndFragment } from '@sentry/utils';
34

45
const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
56

67
const DEFAULT_LINES_OF_CONTEXT = 7;
78

9+
const INTEGRATION_NAME = 'ContextLines';
10+
811
interface ContextLinesOptions {
912
/**
1013
* Sets the number of context lines for each frame when loading a file.
@@ -15,6 +18,17 @@ interface ContextLinesOptions {
1518
frameContextLines?: number;
1619
}
1720

21+
const contextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = {}) => {
22+
const contextLines = options.frameContextLines != null ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
23+
24+
return {
25+
name: INTEGRATION_NAME,
26+
processEvent(event) {
27+
return addSourceContext(event, contextLines);
28+
},
29+
};
30+
};
31+
1832
/**
1933
* Collects source context lines around the lines of stackframes pointing to JS embedded in
2034
* the current page's HTML.
@@ -26,73 +40,41 @@ interface ContextLinesOptions {
2640
* Use this integration if you have inline JS code in HTML pages that can't be accessed
2741
* by our backend (e.g. due to a login-protected page).
2842
*/
29-
export class ContextLines implements Integration {
30-
/**
31-
* @inheritDoc
32-
*/
33-
public static id: string = 'ContextLines';
34-
35-
/**
36-
* @inheritDoc
37-
*/
38-
public name: string;
43+
// eslint-disable-next-line deprecation/deprecation
44+
export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration);
3945

40-
public constructor(private readonly _options: ContextLinesOptions = {}) {
41-
this.name = ContextLines.id;
46+
/**
47+
* Processes an event and adds context lines.
48+
*/
49+
function addSourceContext(event: Event, contextLines: number): Event {
50+
const doc = WINDOW.document;
51+
const htmlFilename = WINDOW.location && stripUrlQueryAndFragment(WINDOW.location.href);
52+
if (!doc || !htmlFilename) {
53+
return event;
4254
}
4355

44-
/**
45-
* @inheritDoc
46-
*/
47-
public setupOnce(_addGlobalEventProcessor: unknown, _getCurrentHub: unknown): void {
48-
// noop
56+
const exceptions = event.exception && event.exception.values;
57+
if (!exceptions || !exceptions.length) {
58+
return event;
4959
}
5060

51-
/** @inheritDoc */
52-
public processEvent(event: Event): Event {
53-
return this.addSourceContext(event);
61+
const html = doc.documentElement.innerHTML;
62+
if (!html) {
63+
return event;
5464
}
5565

56-
/**
57-
* Processes an event and adds context lines.
58-
*
59-
* TODO (v8): Make this internal/private
60-
*/
61-
public addSourceContext(event: Event): Event {
62-
const doc = WINDOW.document;
63-
const htmlFilename = WINDOW.location && stripUrlQueryAndFragment(WINDOW.location.href);
64-
if (!doc || !htmlFilename) {
65-
return event;
66-
}
67-
68-
const exceptions = event.exception && event.exception.values;
69-
if (!exceptions || !exceptions.length) {
70-
return event;
71-
}
66+
const htmlLines = ['<!DOCTYPE html>', '<html>', ...html.split('\n'), '</html>'];
7267

73-
const html = doc.documentElement.innerHTML;
74-
if (!html) {
75-
return event;
68+
exceptions.forEach(exception => {
69+
const stacktrace = exception.stacktrace;
70+
if (stacktrace && stacktrace.frames) {
71+
stacktrace.frames = stacktrace.frames.map(frame =>
72+
applySourceContextToFrame(frame, htmlLines, htmlFilename, contextLines),
73+
);
7674
}
75+
});
7776

78-
const htmlLines = ['<!DOCTYPE html>', '<html>', ...html.split('\n'), '</html>'];
79-
80-
exceptions.forEach(exception => {
81-
const stacktrace = exception.stacktrace;
82-
if (stacktrace && stacktrace.frames) {
83-
stacktrace.frames = stacktrace.frames.map(frame =>
84-
applySourceContextToFrame(
85-
frame,
86-
htmlLines,
87-
htmlFilename,
88-
this._options.frameContextLines != null ? this._options.frameContextLines : DEFAULT_LINES_OF_CONTEXT,
89-
),
90-
);
91-
}
92-
});
93-
94-
return event;
95-
}
77+
return event;
9678
}
9779

9880
/**

0 commit comments

Comments
 (0)