Skip to content

Commit 3290289

Browse files
committed
ref: Always use nextRouterInstrumentation
For ease of use, I make it so that for client-side tracing nextRouterInstrumentation is always turned on
1 parent 84d05f4 commit 3290289

File tree

3 files changed

+79
-16
lines changed

3 files changed

+79
-16
lines changed

packages/nextjs/src/index.client.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,9 @@ const defaultBrowserTracingIntegration = new BrowserTracing({
3131

3232
function createClientIntegrations(integrations?: UserIntegrations): UserIntegrations {
3333
if (integrations) {
34-
const newIntegrations = addIntegration(defaultBrowserTracingIntegration, integrations);
35-
if (Array.isArray(newIntegrations)) {
36-
newIntegrations.forEach(i => {
37-
if (i.name === 'BrowserTracing') {
38-
(i as InstanceType<typeof BrowserTracing>).options.routingInstrumentation = nextRouterInstrumentation;
39-
}
40-
});
41-
}
42-
return newIntegrations;
34+
return addIntegration(defaultBrowserTracingIntegration, integrations, {
35+
BrowserTracing: { keyPath: 'options.routingInstrumentation', value: nextRouterInstrumentation },
36+
});
4337
} else {
4438
return [defaultBrowserTracingIntegration];
4539
}

packages/nextjs/src/utils/userIntegrations.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,35 @@ import { Integration } from '@sentry/types';
33
export type UserFunctionIntegrations = (integrations: Integration[]) => Integration[];
44
export type UserIntegrations = Integration[] | UserFunctionIntegrations;
55

6+
type Options = {
7+
[integrationName: string]:
8+
| {
9+
keyPath: string;
10+
value: unknown;
11+
}
12+
| undefined;
13+
};
14+
15+
/**
16+
* Recursively traverses an object to update an existing nested key.
17+
* Note: The provided key path must include existing properties,
18+
* the function will not create objects while traversing.
19+
*
20+
* @param obj An object to update
21+
* @param value The value to update the nested key with
22+
* @param keyPath The path to the key to update ex. fizz.buzz.foo
23+
*/
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
function setNestedKey(obj: Record<string, any>, keyPath: string, value: unknown): void {
26+
// Ex. foo.bar.zoop will extract foo and bar.zoop
27+
const match = keyPath.match(/([a-z]+)\.(.*)/i);
28+
if (match === null) {
29+
obj[keyPath] = value;
30+
} else {
31+
setNestedKey(obj[match[1]], match[2], value);
32+
}
33+
}
34+
635
/**
736
* Retrieves the patched integrations with the provided integration.
837
*
@@ -12,18 +41,40 @@ export type UserIntegrations = Integration[] | UserFunctionIntegrations;
1241
*
1342
* @param integration The integration to patch, if necessary.
1443
* @param userIntegrations Integrations defined by the user.
44+
* @param options options to update for a particular integration
1545
* @returns Final integrations, patched if necessary.
1646
*/
17-
export function addIntegration(integration: Integration, userIntegrations: UserIntegrations): UserIntegrations {
47+
export function addIntegration(
48+
integration: Integration,
49+
userIntegrations: UserIntegrations,
50+
options: Options = {},
51+
): UserIntegrations {
1852
if (Array.isArray(userIntegrations)) {
19-
return addIntegrationToArray(integration, userIntegrations);
53+
return addIntegrationToArray(integration, userIntegrations, options);
2054
} else {
21-
return addIntegrationToFunction(integration, userIntegrations);
55+
return addIntegrationToFunction(integration, userIntegrations, options);
2256
}
2357
}
2458

25-
function addIntegrationToArray(integration: Integration, userIntegrations: Integration[]): Integration[] {
26-
if (userIntegrations.map(int => int.name).includes(integration.name)) {
59+
function addIntegrationToArray(
60+
integration: Integration,
61+
userIntegrations: Integration[],
62+
options: Options,
63+
): Integration[] {
64+
let includesName = false;
65+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
66+
for (let x = 0; x < userIntegrations.length; x++) {
67+
if (userIntegrations[x].name === integration.name) {
68+
includesName = true;
69+
}
70+
71+
const op = options[userIntegrations[x].name];
72+
if (op) {
73+
setNestedKey(userIntegrations[x], op.keyPath, op.value);
74+
}
75+
}
76+
77+
if (includesName) {
2778
return userIntegrations;
2879
}
2980
return [...userIntegrations, integration];
@@ -32,10 +83,11 @@ function addIntegrationToArray(integration: Integration, userIntegrations: Integ
3283
function addIntegrationToFunction(
3384
integration: Integration,
3485
userIntegrationsFunc: UserFunctionIntegrations,
86+
options: Options,
3587
): UserFunctionIntegrations {
3688
const wrapper: UserFunctionIntegrations = defaultIntegrations => {
3789
const userFinalIntegrations = userIntegrationsFunc(defaultIntegrations);
38-
return addIntegrationToArray(integration, userFinalIntegrations);
90+
return addIntegrationToArray(integration, userFinalIntegrations, options);
3991
};
4092
return wrapper;
4193
}

packages/nextjs/test/index.client.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('Client init()', () => {
7777
expect(integrations).toEqual([expect.any(Integrations.Breadcrumbs), expect.any(BrowserTracing)]);
7878
});
7979

80-
it('uses custom BrowserTracing but uses nextRouterInstrumentation', () => {
80+
it('uses custom BrowserTracing with array option with nextRouterInstrumentation', () => {
8181
init({
8282
integrations: [new BrowserTracing({ idleTimeout: 5000, startTransactionOnLocationChange: false })],
8383
});
@@ -93,5 +93,22 @@ describe('Client init()', () => {
9393
}),
9494
);
9595
});
96+
97+
it('uses custom BrowserTracing with function option with nextRouterInstrumentation', () => {
98+
init({
99+
integrations: () => [new BrowserTracing({ idleTimeout: 5000, startTransactionOnLocationChange: false })],
100+
});
101+
102+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
103+
const integrationFunc = reactInitOptions.integrations as () => Integration[];
104+
const integrations = integrationFunc();
105+
expect((integrations[0] as InstanceType<typeof BrowserTracing>).options).toEqual(
106+
expect.objectContaining({
107+
idleTimeout: 5000,
108+
startTransactionOnLocationChange: false,
109+
routingInstrumentation: nextRouterInstrumentation,
110+
}),
111+
);
112+
});
96113
});
97114
});

0 commit comments

Comments
 (0)