Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
ed011c1
feat(core): Add managed tunnel helpers that derive the tunnel path fr…
logaretm Oct 8, 2026
1ed77e7
ref(core): Pass the whole request to the tunnel matcher and expose `g…
logaretm Oct 15, 2026
9f81b8d
perf(core): Cache the tunnel path per DSN
logaretm Oct 15, 2026
449f3f5
feat(core): Serve a fixed managed tunnel path from a relative `tunnel…
logaretm Oct 15, 2026
be49ea1
feat(core): Drop the server spans of managed tunnel requests
logaretm Oct 15, 2026
2b8e376
fix(core): Skip fetch instrumentation while tracing is suppressed
logaretm Oct 15, 2026
2b03c6a
feat(core): Check the tunnel DSN before reading the envelope body
logaretm Oct 15, 2026
4fcb0cb
fix(core): Cancel the body of a rejected tunnel request
logaretm Oct 15, 2026
2af4e5e
feat(core): Forward the client IP of tunnel requests to Sentry
logaretm Oct 15, 2026
fda805e
fix(core): Match a fixed tunnel path that ends with a slash
logaretm Oct 16, 2026
d348c1b
fix(core): Only answer the managed tunnel under its base path
logaretm Oct 16, 2026
69719b6
feat(core): Always accept the server's own DSN in the managed tunnel
logaretm Oct 16, 2026
95d228f
feat(core): Add `applyTunnelDefaults` so framework SDKs fill in the b…
logaretm Oct 16, 2026
f35b3a3
feat(core): Add `setManagedTunnelBasePath` for servers that learn the…
logaretm Oct 16, 2026
b9d60f8
feat(core): Mark the tunnel handler's responses so the browser can te…
logaretm Oct 16, 2026
0ad10d4
fix(core): Normalize tunnel base paths with one util that doesn't bac…
logaretm Oct 16, 2026
ee9dcd5
fix(core): Leave a fixed tunnel path to the app when there is no DSN …
logaretm Oct 18, 2026
5653798
fix(core): Don't add the base path again to a tunnel path that alread…
logaretm Oct 19, 2026
4a999a3
fix(core): Don't report a client that drops the tunnel request as an …
logaretm Oct 19, 2026
3017e68
fix(core): Also drop tunnel spans that only carry `http.url`, like Sv…
logaretm Oct 19, 2026
c7a419a
feat(core): Add enableManagedTunnel for framework options that enable…
logaretm Oct 19, 2026
c54586d
ref(core): Settle a server client's managed tunnel in one place, from…
logaretm Oct 19, 2026
5a0ddf8
feat: Accept `tunnel: true` in `init` to enable the managed tunnel
logaretm Oct 8, 2026
bf1178f
feat: Explain a managed tunnel that nothing on the server answers
logaretm Oct 15, 2026
9fcc375
fix(core): Keep the managed tunnel warning out of production bundles
logaretm Oct 16, 2026
1237306
fix(aws-serverless): Keep the managed tunnel when the Lambda extensio…
logaretm Oct 16, 2026
b904659
fix(core): Point Next.js users to `tunnelRoute` when the managed tunn…
logaretm Oct 16, 2026
e1905f7
feat(browser): Send directly to Sentry when nothing answers the manag…
logaretm Oct 16, 2026
2f84ea9
fix(browser): Normalize the managed tunnel's base path like the serve…
logaretm Oct 19, 2026
87198eb
fix(browser): Keep the managed tunnel when Sentry answers 404 or 405 …
logaretm Oct 19, 2026
4238303
fix(browser): Send directly when anything but the tunnel answers a ma…
logaretm Oct 19, 2026
04cf6f0
ref(browser): Warn about a rejected DSN from the managed tunnel trans…
logaretm Oct 19, 2026
328fa66
feat(tanstackstart-react): Serve the managed tunnel from the server e…
logaretm Oct 15, 2026
d89155b
feat(tanstackstart-react): Serve the managed tunnel under the base pa…
logaretm Oct 16, 2026
1b4a9c5
fix(tanstackstart-react): Drop the tunnel's spans from the Vite dev s…
logaretm Oct 16, 2026
0414802
fix(tanstackstart-react): Normalize the tunnel base path with the cor…
logaretm Oct 16, 2026
4d9789c
fix(tanstackstart-react): Pass a Worker's env and ctx through the tun…
logaretm Oct 18, 2026
fcdb6e6
fix(tanstackstart-react): Fail the build when a fixed tunnelRoute col…
logaretm Oct 19, 2026
ab02b21
docs(tanstackstart-react): Describe how tunnelRoute is served now
logaretm Oct 19, 2026
a64d809
ref(tanstackstart-react): Enable tunnelRoute on the client like init …
logaretm Oct 19, 2026
76dfbbb
ref(tanstackstart-react): Report tunnelRoute and the base path once w…
logaretm Oct 19, 2026
2689a18
test: Check that runtimes drop the server spans of managed tunnel req…
logaretm Oct 15, 2026
934f55e
test(bun): Run the managed tunnel suite only with the Bun SDK
logaretm Oct 15, 2026
2fbbe93
test(node): Assert the tunnel request sends no server span at all
logaretm Oct 16, 2026
366bdb7
feat(sveltekit): Serve the managed tunnel from `sentryHandle` under t…
logaretm Oct 15, 2026
1d45cb7
fix(sveltekit): Keep source maps of the hooks files under a base path
logaretm Oct 16, 2026
ab3ce59
fix(sveltekit): Serve a relative `tunnel` path under the base path too
logaretm Oct 16, 2026
3699521
test(sveltekit): Check the managed tunnel under `vite dev` too
logaretm Oct 16, 2026
0b4cba0
test(sveltekit): Check no span of a tunnel request is sent with Kit t…
logaretm Oct 19, 2026
7e96182
ref(sveltekit): Import the tunnel handler from @sentry/core/server
logaretm Oct 19, 2026
7496f1d
feat(nuxt, nitro): Serve the managed tunnel before the app's server m…
logaretm Oct 15, 2026
588d663
feat(nuxt, nitro): Serve the managed tunnel under the app's base URL
logaretm Oct 16, 2026
694b885
fix(nitro): Drop the tunnel's dev server spans under `baseURL` too
logaretm Oct 16, 2026
ea8b717
fix(nuxt): Normalize the tunnel base path with the core util
logaretm Oct 16, 2026
1cc04f9
fix(nuxt, nitro): Serve the managed tunnel from `nitroApp.fetch` so r…
logaretm Oct 18, 2026
bd02881
fix(nuxt): Serve the Nitro v2 tunnel from the first h3 layer so route…
logaretm Oct 19, 2026
62d62fd
test(nitro): Check a client dropping the tunnel request isn't reporte…
logaretm Oct 19, 2026
b18e464
fix(nuxt): Serve the managed tunnel when the SDK is set up with sentr…
logaretm Oct 19, 2026
fb58f66
ref(nuxt, nitro): Report the base URL once with setManagedTunnelDefau…
logaretm Oct 19, 2026
34649a5
feat(astro): Serve the managed tunnel from the Sentry middleware
logaretm Oct 15, 2026
b4453e3
fix(astro): Run the server init before the middleware so the tunnel w…
logaretm Oct 16, 2026
a1c95e4
fix(astro): Serve a relative `tunnel` path under the base path too
logaretm Oct 16, 2026
a0a4102
test(astro): Check the managed tunnel under `astro dev` too
logaretm Oct 16, 2026
4d98700
revert(astro): Load the server init from SSR pages only, as on develop
logaretm Oct 19, 2026
bbc97ee
ref(astro): Import the tunnel handler from @sentry/core/server
logaretm Oct 19, 2026
a45041b
feat(remix): Serve the managed tunnel from the Remix 3 router middleware
logaretm Oct 15, 2026
c2a06f6
ref(remix): Import the tunnel handler from @sentry/core/server
logaretm Oct 19, 2026
6069ffe
feat(react-router): Serve the managed tunnel from a route in the serv…
logaretm Oct 15, 2026
13d36b4
test(react-router): Define the tunnel flag in the Cloudflare build
logaretm Oct 15, 2026
eb1d918
fix(react-router): Serve the managed tunnel under the app's `basename`
logaretm Oct 16, 2026
9affda3
test(react-router): Check the managed tunnel under `react-router dev`…
logaretm Oct 16, 2026
f90ba56
fix(react-router): Normalize the tunnel base path with the core util
logaretm Oct 16, 2026
068c11f
fix(react-router): Serve every managed tunnel path, one route each
logaretm Oct 19, 2026
1ccbbae
ref(react-router): Report the basename once with setManagedTunnelDefa…
logaretm Oct 19, 2026
3591362
feat(solidstart): Serve the managed tunnel on SolidStart v1
logaretm Oct 15, 2026
3ef6ec4
fix(solidstart): Serve the managed tunnel under `server.baseURL`
logaretm Oct 16, 2026
d4c1fa8
test(solidstart): Check the managed tunnel on SolidStart 2
logaretm Oct 16, 2026
60f8a5e
test(solidstart): Check the managed tunnel under the SolidStart 2 dev…
logaretm Oct 16, 2026
9ca3f64
test(solidstart): Check the managed tunnel under `vinxi dev` too
logaretm Oct 16, 2026
c101e43
fix(solidstart): Normalize the tunnel base path with the core util
logaretm Oct 16, 2026
deee419
fix(solidstart): Read the tunnel request with h3 so serverless preset…
logaretm Oct 18, 2026
89f6ef6
fix(solidstart): Serve the tunnel from the first h3 layer so route ru…
logaretm Oct 19, 2026
d6f8d7d
ref(solidstart): Report the base path once with setManagedTunnelDefaults
logaretm Oct 19, 2026
65160f0
feat(nextjs): Serve the tunnel from the app with a DSN-derived path
logaretm Oct 15, 2026
e0d1111
fix(nextjs): Read the injected tunnel options when the client has none
logaretm Oct 15, 2026
9ad5e7d
test(nextjs): Wait for a later span instead of a timeout in the tunne…
logaretm Oct 16, 2026
13bb279
fix(nextjs): Write the tunnel route into the root app dir when the ro…
logaretm Oct 16, 2026
43c4a5d
test(nextjs): Wait for a later span instead of a timeout in the pages…
logaretm Oct 16, 2026
bf77f91
fix(nextjs): Point middleware at the tunnel path instead of the envel…
logaretm Oct 16, 2026
1efc017
feat(nextjs): Accept allowed DSNs in `tunnelRoute` for a browser DSN …
logaretm Oct 16, 2026
8cc5098
feat(nextjs): Export `isTunnelRequest` so middleware can let the tunn…
logaretm Oct 16, 2026
b3177a2
fix(nextjs): Serve the server's own DSN path alongside `allowedDsns`
logaretm Oct 16, 2026
5bc562c
ref(nextjs): Import the tunnel helpers from @sentry/core/server
logaretm Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const NO_HTTP_SERVER_SPANS = [
'suites/tracing/httpIntegration/test.ts',
'suites/tracing/httpServerSpans-unrouted/test.ts',
'suites/tracing/ignoreSpans/**',
'suites/tracing/managed-tunnel/test.ts',
'suites/tracing/meta-tags-twp-errors/test.ts',
'suites/tracing/meta-tags/test.ts',
'suites/tracing/requestData/test.ts',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { defineConfig } from 'astro/config';

import node from '@astrojs/node';

// The tunnel variant serves the app under a base path with trailing slashes to cover that the managed tunnel
// follows both.
const tunnelVariant = !!process.env.PUBLIC_E2E_TEST_TUNNEL;

// https://astro.build/config
export default defineConfig({
...(tunnelVariant && { base: '/docs', trailingSlash: 'always' }),
integrations: [
sentry({
debug: true,
Expand Down
19 changes: 17 additions & 2 deletions dev-packages/e2e-tests/test-applications/astro-7/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development playwright test db"
"test:dev": "TEST_ENV=development playwright test db",
"test:build:tunnel": "pnpm install && PUBLIC_E2E_TEST_TUNNEL=1 PUBLIC_E2E_TEST_DSN=http://public@localhost:3031/1337 pnpm build",
"test:assert:tunnel": "PUBLIC_E2E_TEST_TUNNEL=1 PUBLIC_E2E_TEST_DSN=http://public@localhost:3031/1337 TEST_ENV=production playwright test tunnel",
"test:assert:tunnel-dev": "PUBLIC_E2E_TEST_TUNNEL=1 PUBLIC_E2E_TEST_DSN=http://public@localhost:3031/1337 TEST_ENV=development playwright test tunnel"
},
"//": "Need to use ioredis 5.10.1 because that's the last version before they support tracing channels",
"dependencies": {
Expand All @@ -27,6 +30,18 @@
"extends": "../../package.json"
},
"sentryTest": {
"optional": true
"optional": true,
"variants": [
{
"build-command": "pnpm test:build:tunnel",
"assert-command": "pnpm test:assert:tunnel",
"label": "astro-7 (tunnel)"
},
{
"label": "astro-7 (tunnel-dev)",
"build-command": "pnpm install",
"assert-command": "pnpm test:assert:tunnel-dev"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Sentry.init({
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
tunnel: 'http://localhost:3031/', // proxy server
tunnel: import.meta.env.PUBLIC_E2E_TEST_TUNNEL ? true : 'http://localhost:3031/', // proxy server
integrations: [
Sentry.browserTracingIntegration({
beforeStartSpan: opts => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ Sentry.init({
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
tunnel: 'http://localhost:3031/', // proxy server
tunnel: import.meta.env.PUBLIC_E2E_TEST_TUNNEL ? true : 'http://localhost:3031/', // proxy server
debug: true,
});
14 changes: 14 additions & 0 deletions dev-packages/e2e-tests/test-applications/astro-7/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineMiddleware } from 'astro:middleware';

// The tunnel variant checks that the managed tunnel answers before an auth middleware like this one.
export const onRequest = defineMiddleware((context, next) => {
if (
import.meta.env.PUBLIC_E2E_TEST_TUNNEL &&
context.request.method === 'POST' &&
context.request.headers.get('x-auth') !== 'ok'
) {
return new Response('blocked by user auth', { status: 401 });
}

return next();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, test } from '@playwright/test';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

test.skip(!process.env.PUBLIC_E2E_TEST_TUNNEL, 'Only runs in the tunnel variant');

const TUNNEL_PATH = /^\/docs\/[a-z0-9]{1,7}\/$/;

test('answers the managed tunnel under the base path before the app middleware', async ({ page }) => {
// The variant's auth middleware rejects every other POST.
expect((await page.request.post('/docs/anything/', { data: 'x' })).status()).toBe(401);

const tunnelServerSpanPromise = waitForStreamedSpan(
'astro-7',
span =>
getSpanOp(span) === 'http.server' &&
/\/docs\/[a-z0-9]{1,7}\/?$/.test(String(span.attributes['url.path']?.value ?? '')),
);
const tunnelResponsePromise = page.waitForResponse(
response => response.request().method() === 'POST' && TUNNEL_PATH.test(new URL(response.url()).pathname),
);

await page.goto('/docs/');
const tunnelResponse = await tunnelResponsePromise;

expect(tunnelResponse.status()).toBe(200);
expect(new URL(tunnelResponse.url()).origin).toBe(new URL(page.url()).origin);

// Anchor on a regular server span requested after the tunnel traffic: spans flush in order, so a tunnel span
// would arrive first.
const anchorSpanPromise = waitForStreamedSpan(
'astro-7',
span =>
getSpanOp(span) === 'http.server' &&
String(span.attributes['url.path']?.value ?? '').startsWith('/docs/test-ssr'),
);
await page.request.get('/docs/test-ssr/');

const winner = await Promise.race([
tunnelServerSpanPromise.then(() => 'tunnel' as const),
anchorSpanPromise.then(() => 'anchor' as const),
]);
expect(winner).toBe('anchor');
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
// Use a fake but properly formatted Sentry SaaS DSN for tunnel route testing
dsn: 'https://public@o12345.ingest.us.sentry.io/67890',
// The tunnel forwards to the DSN host, which is the event proxy here
dsn: 'http://public@localhost:3031/1337',
// No tunnel option - using tunnelRoute from withSentryConfig
tracesSampleRate: 1.0,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
"private": true,
"scripts": {
"dev": " next dev",
"build": "_SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
"build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
"clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
"dev:webpack": "_SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ next dev --webpack",
"build-webpack": "_SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ next build --webpack > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
"start": "_SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ next start",
"dev:webpack": "next dev --webpack",
"build-webpack": "next build --webpack > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
"start": "next start",
"lint": "eslint",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development _SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ playwright test",
"test:dev-webpack": "TEST_ENV=development-webpack _SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ playwright test",
"test:dev": "TEST_ENV=development playwright test",
"test:dev-webpack": "TEST_ENV=development-webpack playwright test",
"test:build": "pnpm install && pnpm build",
"test:build-webpack": "pnpm install && pnpm build-webpack",
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm build",
Expand All @@ -21,7 +21,7 @@
"test:build-canary-webpack": "pnpm install && pnpm add next@canary && pnpm build-webpack",
"test:assert": "pnpm test:prod && pnpm test:dev",
"test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack",
"test:build:cloudflare": "pnpm install && _SENTRY_TUNNEL_DESTINATION_OVERRIDE=http://localhost:3031/ opennextjs-cloudflare build",
"test:build:cloudflare": "pnpm install && opennextjs-cloudflare build",
"start:cloudflare": "opennextjs-cloudflare preview"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import * as Sentry from '@sentry/nextjs';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function proxy(_request: NextRequest) {
export async function proxy(request: NextRequest) {
// Stands in for auth middleware, without blocking the other tests.
if (request.headers.get('x-e2e-require-auth') && !Sentry.isTunnelRequest(request)) {
return new NextResponse(null, { status: 401 });
}

return NextResponse.next();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
// Use a fake but properly formatted Sentry SaaS DSN for tunnel route testing
dsn: 'https://public@o12345.ingest.us.sentry.io/67890',
// The tunnel forwards to the DSN host, which is the event proxy here
dsn: 'http://public@localhost:3031/1337',
// No tunnel option - using tunnelRoute from withSentryConfig
tracesSampleRate: 1.0,
// debug: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
// Use a fake but properly formatted Sentry SaaS DSN for tunnel route testing
dsn: 'https://public@o12345.ingest.us.sentry.io/67890',
// The tunnel forwards to the DSN host, which is the event proxy here
dsn: 'http://public@localhost:3031/1337',
// No tunnel option - using tunnelRoute from withSentryConfig
tracesSampleRate: 1.0,
// debug: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { expect, test } from '@playwright/test';
import { getSpanOp, waitForError, waitForStreamedSpan, waitForStreamedSpans } from '@sentry-internal/test-utils';
import type { SerializedStreamedSpan } from '@sentry/core';
import { getTunnelPath } from '@sentry/core';

const TUNNEL_PATH = getTunnelPath('http://public@localhost:3031/1337');

test('sends events to the DSN-derived path, served by the app', async ({ page }) => {
const tunnelRequests: string[] = [];
page.on('request', request => {
if (request.method() === 'POST') {
tunnelRequests.push(new URL(request.url()).pathname);
}
});

const errorPromise = waitForError('nextjs-16-tunnel', event => {
return event.exception?.values?.[0]?.value === 'Managed tunnel error';
});

await page.goto('/');
await page.evaluate(() => {
setTimeout(() => {
throw new Error('Managed tunnel error');
});
});

expect(await errorPromise).toBeDefined();
expect(tunnelRequests).toContain(TUNNEL_PATH);
});

test('does not create server spans for tunnel requests', async ({ page }) => {
const serverSpans: SerializedStreamedSpan[] = [];
void waitForStreamedSpans('nextjs-16-tunnel', spans => {
serverSpans.push(...spans.filter(span => span.is_segment));
return false;
});

const errorPromise = waitForError('nextjs-16-tunnel', event => {
return event.exception?.values?.[0]?.value === 'Managed tunnel span error';
});

await page.goto('/');
await page.evaluate(() => {
setTimeout(() => {
throw new Error('Managed tunnel span error');
});
});
await errorPromise;

// Spans flush in order, so a span for the tunnel request would arrive before the one for this request.
const anchorSpanPromise = waitForStreamedSpan(
'nextjs-16-tunnel',
span => span.is_segment && getSpanOp(span) === 'http.server' && span.name === 'GET /',
);
await page.request.get('/');
await anchorSpanPromise;

const tunnelSpans = serverSpans.filter(
span => span.name.includes(TUNNEL_PATH!) || span.name.includes('sentry-tunnel'),
);
expect(tunnelSpans).toEqual([]);
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
logaretm marked this conversation as resolved.
});

test('lets tunnel requests through auth middleware with `isTunnelRequest`', async ({ request }) => {
const headers = { 'content-type': 'application/x-sentry-envelope', 'x-e2e-require-auth': '1' };
const data = `${JSON.stringify({ dsn: 'http://public@localhost:3031/1337' })}\n`;

expect((await request.post(TUNNEL_PATH!, { headers, data })).status()).toBe(200);
expect((await request.post('/notatunnel', { headers, data })).status()).toBe(401);
});

test('answers 404 to an envelope POST on any other path', async ({ request }) => {
const response = await request.post('/notatunnel', {
headers: { 'content-type': 'application/x-sentry-envelope' },
data: `${JSON.stringify({ dsn: 'http://public@localhost:3031/1337' })}\n`,
});

expect(response.status()).toBe(404);
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import handler from './.open-next/worker.js';
export default Sentry.withSentry(
() => ({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: 'https://public@o12345.ingest.us.sentry.io/67890',
dsn: 'http://public@localhost:3031/1337',
// Sends the server spans to the proxy server, so the tests see whether a tunnel request was sampled.
tunnel: 'http://localhost:3031/',
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
// The tunnel forwards to the DSN host, so the tunnel variant points it at the event proxy
dsn: process.env.NEXT_PUBLIC_E2E_TUNNEL ? 'http://public@localhost:3031/1337' : process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: process.env.NEXT_PUBLIC_E2E_TUNNEL ? undefined : `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ export function register() {
if (process.env.NEXT_RUNTIME === 'nodejs' || process.env.NEXT_RUNTIME === 'edge') {
Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
// The tunnel forwards to the DSN host, so the tunnel variant points it at the event proxy
dsn: process.env.NEXT_PUBLIC_E2E_TUNNEL
? 'http://public@localhost:3031/1337'
: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: process.env.NEXT_PUBLIC_E2E_TUNNEL ? undefined : `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
transportOptions: {
// We are doing a lot of events at once in this test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ export async function middleware(request: NextRequest) {
await fetch('http://localhost:3030/');
}

// Stands in for auth middleware, without blocking the other tests.
if (request.headers.has('x-e2e-require-auth') && !Sentry.isTunnelRequest(request)) {
return new NextResponse(null, { status: 401 });
}

return NextResponse.next();
}

// See "Matching Paths" below to learn more
export const config = {
matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'],
// The tunnel path of the test DSN, and a path next to it for the tunnel variant.
matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware', '/qpo967', '/notatunnel'],
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const nextConfig = {

module.exports = withSentryConfig(nextConfig, {
debug: true,
tunnelRoute: !!process.env.NEXT_PUBLIC_E2E_TUNNEL,
sourcemaps: {
deleteSourcemapsAfterUpload: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"test:build": "pnpm install && pnpm build",
"test:test-build": "pnpm ts-node --script-mode assert-build.ts",
"test:build-15": "pnpm install && pnpm add next@15 && pnpm add react@latest && pnpm add react-dom@latest && pnpm build",
"test:assert": "pnpm test:test-build && pnpm test:prod && pnpm test:dev"
"test:assert": "pnpm test:test-build && pnpm test:prod && pnpm test:dev",
"test:build:tunnel": "pnpm install && NEXT_PUBLIC_E2E_TUNNEL=1 pnpm build",
"test:assert:tunnel": "NEXT_PUBLIC_E2E_TUNNEL=1 pnpm test:prod tunnel-dsn-path"
},
"dependencies": {
"@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz",
Expand All @@ -37,6 +39,11 @@
{
"build-command": "pnpm test:build-15",
"label": "nextjs-pages-dir (next@15)"
},
{
"build-command": "pnpm test:build:tunnel",
"assert-command": "pnpm test:assert:tunnel",
"label": "nextjs-pages-dir (tunnel)"
}
],
"optionalVariants": []
Expand Down
Loading
Loading