Skip to content

Commit c38281a

Browse files
committed
add experimental flag
1 parent e47084a commit c38281a

File tree

5 files changed

+117
-30
lines changed

5 files changed

+117
-30
lines changed

packages/nextjs/src/client/clientNormalizationIntegration.ts

+62-29
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,82 @@ import { WINDOW, rewriteFramesIntegration } from '@sentry/browser';
22
import { defineIntegration } from '@sentry/core';
33

44
export const nextjsClientStackFrameNormalizationIntegration = defineIntegration(
5-
({ assetPrefix, basePath }: { assetPrefix?: string; basePath?: string }) => {
6-
const windowOrigin = WINDOW.location.origin;
7-
5+
({
6+
assetPrefix,
7+
basePath,
8+
rewriteFramesAssetPrefixPath,
9+
experimentalThirdPartyOriginStackFrames,
10+
}: {
11+
assetPrefix?: string;
12+
basePath?: string;
13+
rewriteFramesAssetPrefixPath: string;
14+
experimentalThirdPartyOriginStackFrames: boolean;
15+
}) => {
816
const rewriteFramesInstance = rewriteFramesIntegration({
917
// Turn `<origin>/<path>/_next/static/...` into `app:///_next/static/...`
1018
iteratee: frame => {
11-
// A filename starting with the local origin and not ending with JS is most likely JS in HTML which we do not want to rewrite
12-
if (frame.filename?.startsWith(windowOrigin) && !frame.filename.endsWith('.js')) {
13-
return frame;
14-
}
19+
if (experimentalThirdPartyOriginStackFrames) {
20+
const windowOrigin = WINDOW.location.origin;
21+
// A filename starting with the local origin and not ending with JS is most likely JS in HTML which we do not want to rewrite
22+
if (frame.filename?.startsWith(windowOrigin) && !frame.filename.endsWith('.js')) {
23+
return frame;
24+
}
1525

16-
if (assetPrefix) {
17-
// If the user defined an asset prefix, we need to strip it so that we can match it with uploaded sourcemaps.
18-
// assetPrefix always takes priority over basePath.
19-
if (frame.filename?.startsWith(assetPrefix)) {
20-
frame.filename = frame.filename.replace(assetPrefix, 'app://');
26+
if (assetPrefix) {
27+
// If the user defined an asset prefix, we need to strip it so that we can match it with uploaded sourcemaps.
28+
// assetPrefix always takes priority over basePath.
29+
if (frame.filename?.startsWith(assetPrefix)) {
30+
frame.filename = frame.filename.replace(assetPrefix, 'app://');
31+
}
32+
} else if (basePath) {
33+
// If the user defined a base path, we need to strip it to match with uploaded sourcemaps.
34+
// We should only do this for same-origin filenames though, so that third party assets are not rewritten.
35+
try {
36+
const { origin: frameOrigin } = new URL(frame.filename as string);
37+
if (frameOrigin === windowOrigin) {
38+
frame.filename = frame.filename?.replace(frameOrigin, 'app://').replace(basePath, '');
39+
}
40+
} catch (err) {
41+
// Filename wasn't a properly formed URL, so there's nothing we can do
42+
}
2143
}
22-
} else if (basePath) {
23-
// If the user defined a base path, we need to strip it to match with uploaded sourcemaps.
24-
// We should only do this for same-origin filenames though, so that third party assets are not rewritten.
44+
} else {
2545
try {
26-
const { origin: frameOrigin } = new URL(frame.filename as string);
27-
if (frameOrigin === windowOrigin) {
28-
frame.filename = frame.filename?.replace(frameOrigin, 'app://').replace(basePath, '');
29-
}
46+
const { origin } = new URL(frame.filename as string);
47+
frame.filename = frame.filename?.replace(origin, 'app://').replace(rewriteFramesAssetPrefixPath, '');
3048
} catch (err) {
3149
// Filename wasn't a properly formed URL, so there's nothing we can do
3250
}
3351
}
3452

3553
// We need to URI-decode the filename because Next.js has wildcard routes like "/users/[id].js" which show up as "/users/%5id%5.js" in Error stacktraces.
3654
// The corresponding sources that Next.js generates have proper brackets so we also need proper brackets in the frame so that source map resolving works.
37-
if (frame.filename?.includes('/_next')) {
38-
frame.filename = decodeURI(frame.filename);
39-
}
55+
if (experimentalThirdPartyOriginStackFrames) {
56+
if (frame.filename?.includes('/_next')) {
57+
frame.filename = decodeURI(frame.filename);
58+
}
59+
60+
if (
61+
frame.filename?.match(
62+
/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
63+
)
64+
) {
65+
// We don't care about these frames. It's Next.js internal code.
66+
frame.in_app = false;
67+
}
68+
} else {
69+
if (frame.filename?.startsWith('app:///_next')) {
70+
frame.filename = decodeURI(frame.filename);
71+
}
4072

41-
if (
42-
frame.filename?.match(
43-
/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
44-
)
45-
) {
46-
// We don't care about these frames. It's Next.js internal code.
47-
frame.in_app = false;
73+
if (
74+
frame.filename?.match(
75+
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
76+
)
77+
) {
78+
// We don't care about these frames. It's Next.js internal code.
79+
frame.in_app = false;
80+
}
4881
}
4982

5083
return frame;

packages/nextjs/src/client/index.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ export { captureUnderscoreErrorException } from '../common/pages-router-instrume
1616
export { browserTracingIntegration } from './browserTracingIntegration';
1717

1818
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
19+
_sentryRewriteFramesAssetPrefixPath: string;
1920
_sentryAssetPrefix?: string;
2021
_sentryBasePath?: string;
22+
_experimentalThirdPartyOriginStackFrames?: string;
2123
};
2224

2325
// Treeshakable guard to remove all code related to tracing
@@ -70,9 +72,23 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] {
7072

7173
// These values are injected at build time, based on the output directory specified in the build config. Though a default
7274
// is set there, we set it here as well, just in case something has gone wrong with the injection.
75+
const rewriteFramesAssetPrefixPath =
76+
process.env._sentryRewriteFramesAssetPrefixPath ||
77+
globalWithInjectedValues._sentryRewriteFramesAssetPrefixPath ||
78+
'';
7379
const assetPrefix = process.env._sentryAssetPrefix || globalWithInjectedValues._sentryAssetPrefix;
7480
const basePath = process.env._sentryBasePath || globalWithInjectedValues._sentryBasePath;
75-
customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefix, basePath }));
81+
const experimentalThirdPartyOriginStackFrames =
82+
process.env._experimentalThirdPartyOriginStackFrames === 'true' ||
83+
globalWithInjectedValues._experimentalThirdPartyOriginStackFrames === 'true';
84+
customDefaultIntegrations.push(
85+
nextjsClientStackFrameNormalizationIntegration({
86+
assetPrefix,
87+
basePath,
88+
rewriteFramesAssetPrefixPath,
89+
experimentalThirdPartyOriginStackFrames,
90+
}),
91+
);
7692

7793
return customDefaultIntegrations;
7894
}

packages/nextjs/src/config/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,16 @@ export type SentryBuildOptions = {
439439
* Defaults to `false`.
440440
*/
441441
automaticVercelMonitors?: boolean;
442+
443+
/**
444+
* Contains a set of experimental flags that might change in future releases. These flags enable
445+
* features that are still in development and may be modified, renamed, or removed without notice.
446+
* Use with caution in production environments.
447+
*
448+
*/
449+
_experimental?: Partial<{
450+
thirdPartyOriginStackFrames: boolean;
451+
}>;
442452
};
443453

444454
export type NextConfigFunction = (

packages/nextjs/src/config/webpack.ts

+10
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,8 @@ function addValueInjectionLoader(
595595
buildContext: BuildContext,
596596
releaseName: string | undefined,
597597
): void {
598+
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
599+
598600
const isomorphicValues = {
599601
// `rewritesTunnel` set by the user in Next.js config
600602
_sentryRewritesTunnelPath:
@@ -617,7 +619,15 @@ function addValueInjectionLoader(
617619

618620
const clientValues = {
619621
...isomorphicValues,
622+
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
623+
// `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
624+
_sentryRewriteFramesAssetPrefixPath: assetPrefix
625+
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
626+
: '',
620627
_sentryAssetPrefix: userNextConfig.assetPrefix,
628+
_sentryExperimentalThirdPartyOriginStackFrames: userSentryOptions._experimental?.thirdPartyOriginStackFrames
629+
? 'true'
630+
: undefined,
621631
};
622632

623633
if (buildContext.isServer) {

packages/nextjs/src/config/withSentryConfig.ts

+18
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s
250250

251251
// TODO: For Turbopack we need to pass the release name here and pick it up in the SDK
252252
function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void {
253+
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
253254
const basePath = userNextConfig.basePath ?? '';
254255
const rewritesTunnelPath =
255256
userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export'
@@ -260,8 +261,21 @@ function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOpt
260261
// Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape
261262
// characters)
262263
_sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next',
264+
// Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if
265+
// `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.)
266+
_sentryRewriteFramesAssetPrefixPath: assetPrefix
267+
? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '')
268+
: '',
263269
};
264270

271+
if (userNextConfig.assetPrefix) {
272+
buildTimeVariables._assetsPrefix = userNextConfig.assetPrefix;
273+
}
274+
275+
if (userSentryOptions._experimental?.thirdPartyOriginStackFrames) {
276+
buildTimeVariables._experimentalThirdPartyOriginStackFrames = 'true';
277+
}
278+
265279
if (rewritesTunnelPath) {
266280
buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath;
267281
}
@@ -274,6 +288,10 @@ function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOpt
274288
buildTimeVariables._sentryAssetPrefix = userNextConfig.assetPrefix;
275289
}
276290

291+
if (userSentryOptions._experimental?.thirdPartyOriginStackFrames) {
292+
buildTimeVariables._experimentalThirdPartyOriginStackFrames = 'true';
293+
}
294+
277295
if (typeof userNextConfig.env === 'object') {
278296
userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env };
279297
} else if (userNextConfig.env === undefined) {

0 commit comments

Comments
 (0)