Skip to content

Commit a230c8b

Browse files
committed
fix: esnure random path is preserved through env
1 parent 49badb0 commit a230c8b

File tree

4 files changed

+132
-3
lines changed

4 files changed

+132
-3
lines changed

packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,17 @@ export function constructTurbopackConfig({
3434
...(shouldEnableNativeDebugIds ? { debugIds: true } : {}),
3535
};
3636

37+
const tunnelPath =
38+
userSentryOptions.tunnelRoute !== undefined &&
39+
userNextConfig.output !== 'export' &&
40+
typeof userSentryOptions.tunnelRoute === 'string'
41+
? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}`
42+
: undefined;
43+
3744
const valueInjectionRules = generateValueInjectionRules({
3845
routeManifest,
3946
nextJsVersion,
47+
tunnelPath,
4048
});
4149

4250
for (const { matcher, rule } of valueInjectionRules) {

packages/nextjs/src/config/turbopack/generateValueInjectionRules.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import type { JSONValue, TurbopackMatcherWithRule } from '../types';
88
export function generateValueInjectionRules({
99
routeManifest,
1010
nextJsVersion,
11+
tunnelPath,
1112
}: {
1213
routeManifest?: RouteManifest;
1314
nextJsVersion?: string;
15+
tunnelPath?: string;
1416
}): TurbopackMatcherWithRule[] {
1517
const rules: TurbopackMatcherWithRule[] = [];
1618
const isomorphicValues: Record<string, JSONValue> = {};
@@ -26,6 +28,11 @@ export function generateValueInjectionRules({
2628
clientValues._sentryRouteManifest = JSON.stringify(routeManifest);
2729
}
2830

31+
// Inject tunnel route path for both client and server
32+
if (tunnelPath) {
33+
isomorphicValues._sentryRewritesTunnelPath = tunnelPath;
34+
}
35+
2936
if (Object.keys(isomorphicValues).length > 0) {
3037
clientValues = { ...clientValues, ...isomorphicValues };
3138
serverValues = { ...serverValues, ...isomorphicValues };

packages/nextjs/src/config/withSentryConfig.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
supportsProductionCompileHook,
2424
} from './util';
2525
import { constructWebpackConfigFunction } from './webpack';
26+
import { isBuild } from '../common/utils/isBuild';
2627

2728
let showedExportModeTunnelWarning = false;
2829
let showedExperimentalBuildModeWarning = false;
@@ -121,11 +122,10 @@ function getFinalConfigObject(
121122
);
122123
}
123124
} else {
124-
const resolvedTunnelRoute =
125-
userSentryOptions.tunnelRoute === true ? generateRandomTunnelRoute() : userSentryOptions.tunnelRoute;
126-
127125
// Update the global options object to use the resolved value everywhere
126+
const resolvedTunnelRoute = resolveTunnelRoute(userSentryOptions.tunnelRoute);
128127
userSentryOptions.tunnelRoute = resolvedTunnelRoute || undefined;
128+
129129
setUpTunnelRewriteRules(incomingUserNextConfigObject, resolvedTunnelRoute);
130130
}
131131
}
@@ -550,3 +550,26 @@ function getInstrumentationClientFileContents(): string | void {
550550
}
551551
}
552552
}
553+
554+
/**
555+
* Resolves the tunnel route based on the user's configuration and the environment.
556+
* @param tunnelRoute - The user-provided tunnel route option
557+
*/
558+
function resolveTunnelRoute(tunnelRoute: string | true): string {
559+
if (process.env.__SENTRY_TUNNEL_ROUTE__) {
560+
// Reuse cached value from previous build (server/client)
561+
return process.env.__SENTRY_TUNNEL_ROUTE__;
562+
}
563+
564+
const resolvedTunnelRoute = typeof tunnelRoute === 'string' ? tunnelRoute : generateRandomTunnelRoute();
565+
566+
// Cache for subsequent builds (only during build time)
567+
// Turbopack runs the config twice, so we need a shared context to avoid generating a new tunnel route for each build.
568+
// env works well here
569+
// https://linear.app/getsentry/issue/JS-549/adblock-plus-blocking-requests-to-sentry-and-monitoring-tunnel
570+
if (resolvedTunnelRoute) {
571+
process.env.__SENTRY_TUNNEL_ROUTE__ = resolvedTunnelRoute;
572+
}
573+
574+
return resolvedTunnelRoute;
575+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2+
3+
describe('Tunnel Route Caching (Environment Variable)', () => {
4+
let originalNextPhase: string | undefined;
5+
let originalTunnelRoute: string | undefined;
6+
7+
beforeEach(() => {
8+
// Save and clear env vars
9+
originalNextPhase = process.env.NEXT_PHASE;
10+
originalTunnelRoute = process.env.__SENTRY_TUNNEL_ROUTE__;
11+
delete process.env.__SENTRY_TUNNEL_ROUTE__;
12+
});
13+
14+
afterEach(() => {
15+
// Restore env vars
16+
if (originalNextPhase !== undefined) {
17+
process.env.NEXT_PHASE = originalNextPhase;
18+
} else {
19+
delete process.env.NEXT_PHASE;
20+
}
21+
22+
if (originalTunnelRoute !== undefined) {
23+
process.env.__SENTRY_TUNNEL_ROUTE__ = originalTunnelRoute;
24+
} else {
25+
delete process.env.__SENTRY_TUNNEL_ROUTE__;
26+
}
27+
});
28+
29+
it('caches tunnel route in environment variable during build phase', () => {
30+
process.env.NEXT_PHASE = 'phase-production-build';
31+
process.env.__SENTRY_TUNNEL_ROUTE__ = '/cached-route-123';
32+
33+
// The env var should be accessible
34+
expect(process.env.__SENTRY_TUNNEL_ROUTE__).toBe('/cached-route-123');
35+
});
36+
37+
it('environment variable persists across different contexts', () => {
38+
process.env.NEXT_PHASE = 'phase-production-build';
39+
process.env.__SENTRY_TUNNEL_ROUTE__ = '/test-route-456';
40+
41+
// Simulate accessing from different module
42+
const cachedRoute = process.env.__SENTRY_TUNNEL_ROUTE__;
43+
44+
expect(cachedRoute).toBe('/test-route-456');
45+
});
46+
47+
it('verifies NEXT_PHASE detection for build time', () => {
48+
process.env.NEXT_PHASE = 'phase-production-build';
49+
50+
const isBuildTime = process.env.NEXT_PHASE === 'phase-production-build';
51+
52+
expect(isBuildTime).toBe(true);
53+
});
54+
55+
it('verifies NEXT_PHASE detection for non-build time', () => {
56+
process.env.NEXT_PHASE = 'phase-development-server';
57+
58+
const isBuildTime = process.env.NEXT_PHASE === 'phase-production-build';
59+
60+
expect(isBuildTime).toBe(false);
61+
});
62+
63+
it('handles missing NEXT_PHASE', () => {
64+
delete process.env.NEXT_PHASE;
65+
66+
const isBuildTime = process.env.NEXT_PHASE === 'phase-production-build';
67+
68+
expect(isBuildTime).toBe(false);
69+
});
70+
});
71+
72+
describe('Random Tunnel Route Generation', () => {
73+
it('generates an 8-character alphanumeric string', () => {
74+
const randomString = Math.random().toString(36).substring(2, 10);
75+
const tunnelRoute = `/${randomString}`;
76+
77+
// Should be a path with 8 alphanumeric chars
78+
expect(tunnelRoute).toMatch(/^\/[a-z0-9]{8}$/);
79+
});
80+
81+
it('generates different values on multiple calls', () => {
82+
const route1 = `/${Math.random().toString(36).substring(2, 10)}`;
83+
const route2 = `/${Math.random().toString(36).substring(2, 10)}`;
84+
85+
// Very unlikely to be the same (but not impossible)
86+
// This is more of a sanity check
87+
expect(route1).toMatch(/^\/[a-z0-9]{8}$/);
88+
expect(route2).toMatch(/^\/[a-z0-9]{8}$/);
89+
});
90+
});
91+

0 commit comments

Comments
 (0)