From 369fc527f0bd0bd6060f5b8482c2f25b1ef7de9a Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 7 Mar 2024 16:19:35 +0100 Subject: [PATCH] feat(nextjs/v7): Support Hybrid Cloud DSNs with `tunnelRoute` option (#10958) Co-authored-by: Alexander Tarasov Fixes https://github.com/getsentry/sentry-javascript/issues/10948 --- packages/nextjs/src/client/tunnelRoute.ts | 8 +++-- .../nextjs/src/config/withSentryConfig.ts | 32 ++++++++++++++++--- .../nextjs/test/utils/tunnelRoute.test.ts | 11 +++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/nextjs/src/client/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts index 6ac5fb50a640..b3511e7ab70f 100644 --- a/packages/nextjs/src/client/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -17,10 +17,14 @@ export function applyTunnelRouteOption(options: BrowserOptions): void { if (!dsnComponents) { return; } - const sentrySaasDsnMatch = dsnComponents.host.match(/^o(\d+)\.ingest\.sentry\.io$/); + const sentrySaasDsnMatch = dsnComponents.host.match(/^o(\d+)\.ingest(?:\.([a-z]{2}))?\.sentry\.io$/); if (sentrySaasDsnMatch) { const orgId = sentrySaasDsnMatch[1]; - const tunnelPath = `${tunnelRouteOption}?o=${orgId}&p=${dsnComponents.projectId}`; + const regionCode = sentrySaasDsnMatch[2]; + let tunnelPath = `${tunnelRouteOption}?o=${orgId}&p=${dsnComponents.projectId}`; + if (regionCode) { + tunnelPath += `&r=${regionCode}`; + } options.tunnel = tunnelPath; DEBUG_BUILD && logger.info(`Tunneling events to "${tunnelPath}"`); } else { diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index a665aa892c8c..ac43a6f03549 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -96,7 +96,7 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s // This function doesn't take any arguments at the time of writing but we future-proof // here in case Next.js ever decides to pass some userNextConfig.rewrites = async (...args: unknown[]) => { - const injectedRewrite = { + const tunnelRouteRewrite = { // Matched rewrite routes will look like the following: `[tunnelPath]?o=[orgid]&p=[projectid]` // Nextjs will automatically convert `source` into a regex for us source: `${tunnelPath}(/?)`, @@ -115,19 +115,43 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s destination: 'https://o:orgid.ingest.sentry.io/api/:projectid/envelope/?hsts=0', }; + const tunnelRouteRewriteWithRegion = { + // Matched rewrite routes will look like the following: `[tunnelPath]?o=[orgid]&p=[projectid]?r=[region]` + // Nextjs will automatically convert `source` into a regex for us + source: `${tunnelPath}(/?)`, + has: [ + { + type: 'query', + key: 'o', // short for orgId - we keep it short so matching is harder for ad-blockers + value: '(?\\d*)', + }, + { + type: 'query', + key: 'p', // short for projectId - we keep it short so matching is harder for ad-blockers + value: '(?\\d*)', + }, + { + type: 'query', + key: 'r', // short for region - we keep it short so matching is harder for ad-blockers + value: '(?\\[a-z\\]{2})', + }, + ], + destination: 'https://o:orgid.ingest.:region.sentry.io/api/:projectid/envelope/?hsts=0', + }; + if (typeof originalRewrites !== 'function') { - return [injectedRewrite]; + return [tunnelRouteRewriteWithRegion, tunnelRouteRewrite]; } // @ts-expect-error Expected 0 arguments but got 1 - this is from the future-proofing mentioned above, so we don't care about it const originalRewritesResult = await originalRewrites(...args); if (Array.isArray(originalRewritesResult)) { - return [injectedRewrite, ...originalRewritesResult]; + return [tunnelRouteRewriteWithRegion, tunnelRouteRewrite, ...originalRewritesResult]; } else { return { ...originalRewritesResult, - beforeFiles: [injectedRewrite, ...(originalRewritesResult.beforeFiles || [])], + beforeFiles: [tunnelRouteRewriteWithRegion, tunnelRouteRewrite, ...(originalRewritesResult.beforeFiles || [])], }; } }; diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index 62b2fb28cf9c..576898c061b2 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -64,4 +64,15 @@ describe('applyTunnelRouteOption()', () => { expect(options.tunnel).toBeUndefined(); }); + + it('Correctly applies `tunnelRoute` option to region DSNs', () => { + globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + const options: any = { + dsn: 'https://11111111111111111111111111111111@o2222222.ingest.us.sentry.io/3333333', + } as BrowserOptions; + + applyTunnelRouteOption(options); + + expect(options.tunnel).toBe('/my-error-monitoring-route?o=2222222&p=3333333&r=us'); + }); });