Skip to content

Commit 2be79a5

Browse files
committed
Merge remote-tracking branch 'origin/master' into lforst-refactor-proxy-loader
2 parents 4a6acd7 + 7316b85 commit 2be79a5

35 files changed

+236
-168
lines changed

docs/event-sending.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ This document gives an outline for how event sending works, and which which plac
7777
## Replay (WIP)
7878

7979
* `replay.sendReplayRequest()`
80-
* `createPayload()`
81-
* `getReplayEvent()`
80+
* `createRecordingData()`
81+
* `prepareReplayEvent()`
8282
* `client._prepareEvent()` (see baseclient)
8383
* `baseclient._applyClientOptions()`
8484
* `baseclient._applyIntegrationsMetadata()`

packages/nextjs/src/config/loaders/wrappingLoader.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type LoaderOptions = {
2222
pagesDir: string;
2323
pageExtensionRegex: string;
2424
excludeServerRoutes: Array<RegExp | string>;
25+
isEdgeRuntime: boolean;
2526
};
2627

2728
/**
@@ -33,16 +34,22 @@ export default function wrappingLoader(
3334
this: LoaderThis<LoaderOptions>,
3435
userCode: string,
3536
userModuleSourceMap: any,
36-
): void {
37-
this.async();
38-
37+
): void | string {
3938
// We know one or the other will be defined, depending on the version of webpack being used
4039
const {
4140
pagesDir,
4241
pageExtensionRegex,
4342
excludeServerRoutes = [],
43+
isEdgeRuntime,
4444
} = 'getOptions' in this ? this.getOptions() : this.query;
4545

46+
// We currently don't support the edge runtime
47+
if (isEdgeRuntime) {
48+
return userCode;
49+
}
50+
51+
this.async();
52+
4653
// Get the parameterized route name from this page's filepath
4754
const parameterizedRoute = path
4855
// Get the path of the file insde of the pages directory

packages/nextjs/src/config/templates/apiWrapperTemplate.ts

+2-16
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type NextApiModule = (
2323
}
2424
// CJS export
2525
| NextApiHandler
26-
) & { config?: PageConfig & { runtime?: string } };
26+
) & { config?: PageConfig };
2727

2828
const userApiModule = origModule as NextApiModule;
2929

@@ -53,21 +53,7 @@ export const config = {
5353
},
5454
};
5555

56-
// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js
57-
// v12.2.1-canary.3 onwards:
58-
// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206
59-
// https://edge-runtime.vercel.sh/features/available-apis#addressing-the-runtime
60-
declare const EdgeRuntime: string | undefined;
61-
62-
let exportedHandler;
63-
64-
if (typeof EdgeRuntime === 'string') {
65-
exportedHandler = userProvidedHandler;
66-
} else {
67-
exportedHandler = userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, '__ROUTE__') : undefined;
68-
}
69-
70-
export default exportedHandler;
56+
export default userProvidedHandler ? Sentry.withSentryAPI(userProvidedHandler, '__ROUTE__') : undefined;
7157

7258
// Re-export anything exported by the page module we're wrapping. When processing this code, Rollup is smart enough to
7359
// not include anything whose name matchs something we've explicitly exported above.

packages/nextjs/src/config/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export type BuildContext = {
137137
// eslint-disable-next-line @typescript-eslint/no-explicit-any
138138
defaultLoaders: any;
139139
totalPages: number;
140-
nextRuntime?: 'nodejs' | 'edge';
140+
nextRuntime?: 'nodejs' | 'edge'; // Added in Next.js 12+
141141
};
142142

143143
/**

packages/nextjs/src/config/webpack.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ export function constructWebpackConfigFunction(
8585
// Add a loader which will inject code that sets global values
8686
addValueInjectionLoader(newConfig, userNextConfig, userSentryOptions);
8787

88+
if (buildContext.nextRuntime === 'edge') {
89+
// eslint-disable-next-line no-console
90+
console.warn(
91+
'[@sentry/nextjs] You are using edge functions or middleware. Please note that Sentry does not yet support error monitoring for these features.',
92+
);
93+
}
94+
8895
if (isServer) {
8996
if (userSentryOptions.autoInstrumentServerFunctions !== false) {
9097
const pagesDir = newConfig.resolve?.alias?.['private-next-pages'] as string;
@@ -103,6 +110,7 @@ export function constructWebpackConfigFunction(
103110
pagesDir,
104111
pageExtensionRegex,
105112
excludeServerRoutes: userSentryOptions.excludeServerRoutes,
113+
isEdgeRuntime: buildContext.nextRuntime === 'edge',
106114
},
107115
},
108116
],
@@ -306,7 +314,15 @@ async function addSentryToEntryProperty(
306314

307315
// inject into all entry points which might contain user's code
308316
for (const entryPointName in newEntryProperty) {
309-
if (shouldAddSentryToEntryPoint(entryPointName, isServer, userSentryOptions.excludeServerRoutes, isDev)) {
317+
if (
318+
shouldAddSentryToEntryPoint(
319+
entryPointName,
320+
isServer,
321+
userSentryOptions.excludeServerRoutes,
322+
isDev,
323+
buildContext.nextRuntime === 'edge',
324+
)
325+
) {
310326
addFilesToExistingEntryPoint(newEntryProperty, entryPointName, filesToInject);
311327
} else {
312328
if (
@@ -433,7 +449,13 @@ function shouldAddSentryToEntryPoint(
433449
isServer: boolean,
434450
excludeServerRoutes: Array<string | RegExp> = [],
435451
isDev: boolean,
452+
isEdgeRuntime: boolean,
436453
): boolean {
454+
// We don't support the Edge runtime yet
455+
if (isEdgeRuntime) {
456+
return false;
457+
}
458+
437459
// On the server side, by default we inject the `Sentry.init()` code into every page (with a few exceptions).
438460
if (isServer) {
439461
const entryPointRoute = entryPointName.replace(/^pages/, '');
@@ -530,7 +552,13 @@ export function getWebpackPluginOptions(
530552
stripPrefix: ['webpack://_N_E/'],
531553
urlPrefix,
532554
entries: (entryPointName: string) =>
533-
shouldAddSentryToEntryPoint(entryPointName, isServer, userSentryOptions.excludeServerRoutes, isDev),
555+
shouldAddSentryToEntryPoint(
556+
entryPointName,
557+
isServer,
558+
userSentryOptions.excludeServerRoutes,
559+
isDev,
560+
buildContext.nextRuntime === 'edge',
561+
),
534562
release: getSentryRelease(buildId),
535563
dryRun: isDev,
536564
});

packages/nextjs/src/config/wrappers/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import type { NextApiRequest, NextApiResponse } from 'next';
2525
export type NextApiHandler = {
2626
(req: NextApiRequest, res: NextApiResponse): void | Promise<void> | unknown | Promise<unknown>;
2727
__sentry_route__?: string;
28+
29+
/**
30+
* A property we set in our integration tests to simulate running an API route on platforms that don't support streaming.
31+
*/
32+
__sentry_test_doesnt_support_streaming__?: true;
2833
};
2934

3035
export type WrappedNextApiHandler = {

packages/nextjs/src/config/wrappers/utils/responseEnd.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export function autoEndTransactionOnResponseEnd(transaction: Transaction, res: S
3131
};
3232

3333
// Prevent double-wrapping
34-
if (!(res.end as WrappedResponseEndMethod).__sentry_original__) {
34+
// res.end may be undefined during build when using `next export` to statically export a Next.js app
35+
if (res.end && !(res.end as WrappedResponseEndMethod).__sentry_original__) {
3536
fill(res, 'end', wrapEndMethod);
3637
}
3738
}

packages/nextjs/src/config/wrappers/withSentryAPI.ts

+4-24
Original file line numberDiff line numberDiff line change
@@ -130,31 +130,11 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
130130
);
131131
currentScope.setSpan(transaction);
132132

133-
if (platformSupportsStreaming()) {
133+
if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) {
134134
autoEndTransactionOnResponseEnd(transaction, res);
135135
} else {
136-
// If we're not on a platform that supports streaming, we're blocking all response-ending methods until the
137-
// queue is flushed.
138-
139-
const origResSend = res.send;
140-
res.send = async function (this: unknown, ...args: unknown[]) {
141-
if (transaction) {
142-
await finishTransaction(transaction, res);
143-
await flushQueue();
144-
}
145-
146-
origResSend.apply(this, args);
147-
};
148-
149-
const origResJson = res.json;
150-
res.json = async function (this: unknown, ...args: unknown[]) {
151-
if (transaction) {
152-
await finishTransaction(transaction, res);
153-
await flushQueue();
154-
}
155-
156-
origResJson.apply(this, args);
157-
};
136+
// If we're not on a platform that supports streaming, we're blocking res.end() until the queue is flushed.
137+
// res.json() and res.send() will implicitly call res.end(), so it is enough to wrap res.end().
158138

159139
// eslint-disable-next-line @typescript-eslint/unbound-method
160140
const origResEnd = res.end;
@@ -223,7 +203,7 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
223203
// moment they detect an error, so it's important to get this done before rethrowing the error. Apps not
224204
// deployed serverlessly will run into this cleanup code again in `res.end(), but the transaction will already
225205
// be finished and the queue will already be empty, so effectively it'll just no-op.)
226-
if (platformSupportsStreaming()) {
206+
if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) {
227207
void finishTransaction(transaction, res);
228208
} else {
229209
await finishTransaction(transaction, res);

packages/nextjs/src/index.client.ts

-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { RewriteFrames } from '@sentry/integrations';
22
import { configureScope, init as reactInit, Integrations } from '@sentry/react';
33
import { BrowserTracing, defaultRequestInstrumentationOptions, hasTracingEnabled } from '@sentry/tracing';
44
import { EventProcessor } from '@sentry/types';
5-
import { logger } from '@sentry/utils';
65

76
import { nextRouterInstrumentation } from './performance/client';
87
import { buildMetadata } from './utils/metadata';
@@ -31,26 +30,12 @@ export { BrowserTracing };
3130
// Treeshakable guard to remove all code related to tracing
3231
declare const __SENTRY_TRACING__: boolean;
3332

34-
// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js
35-
// v12.2.1-canary.3 onwards:
36-
// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206
37-
// https://edge-runtime.vercel.sh/features/available-apis#addressing-the-runtime
38-
declare const EdgeRuntime: string | undefined;
39-
4033
const globalWithInjectedValues = global as typeof global & {
4134
__rewriteFramesAssetPrefixPath__: string;
4235
};
4336

4437
/** Inits the Sentry NextJS SDK on the browser with the React SDK. */
4538
export function init(options: NextjsOptions): void {
46-
if (typeof EdgeRuntime === 'string') {
47-
// If the SDK is imported when using the Vercel Edge Runtime, it will import the browser SDK, even though it is
48-
// running the server part of a Next.js application. We can use the `EdgeRuntime` to check for that case and make
49-
// the init call a no-op. This will prevent the SDK from crashing on the Edge Runtime.
50-
__DEBUG_BUILD__ && logger.log('Vercel Edge Runtime detected. Will not initialize SDK.');
51-
return;
52-
}
53-
5439
applyTunnelRouteOption(options);
5540
buildMetadata(options, ['nextjs', 'react']);
5641
options.environment = options.environment || process.env.NODE_ENV;

packages/nextjs/src/index.server.ts

-11
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@ const globalWithInjectedValues = global as typeof global & {
3030

3131
const domain = domainModule as typeof domainModule & { active: (domainModule.Domain & Carrier) | null };
3232

33-
// This is a variable that Next.js will string replace during build with a string if run in an edge runtime from Next.js
34-
// v12.2.1-canary.3 onwards:
35-
// https://github.com/vercel/next.js/blob/166e5fb9b92f64c4b5d1f6560a05e2b9778c16fb/packages/next/build/webpack-config.ts#L206
36-
// https://edge-runtime.vercel.sh/features/available-apis#addressing-the-runtime
37-
declare const EdgeRuntime: string | undefined;
38-
3933
// Exporting this constant means we can compute it without the linter complaining, even if we stop directly using it in
4034
// this file. It's important that it be computed as early as possible, because one of its indicators is seeing 'build'
4135
// (as in the CLI command `next build`) in `process.argv`. Later on in the build process, everything's been spun out
@@ -51,11 +45,6 @@ export function init(options: NextjsOptions): void {
5145
logger.enable();
5246
}
5347

54-
if (typeof EdgeRuntime === 'string') {
55-
__DEBUG_BUILD__ && logger.log('Vercel Edge Runtime detected. Will not initialize SDK.');
56-
return;
57-
}
58-
5948
__DEBUG_BUILD__ && logger.log('Initializing SDK...');
6049

6150
if (sdkAlreadyInitialized()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NextApiRequest, NextApiResponse } from 'next';
2+
3+
const handler = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
4+
// This handler calls .end twice. We test this to verify that this still doesn't throw because we're wrapping `.end`.
5+
res.status(200).json({ success: true });
6+
res.end();
7+
};
8+
9+
handler.__sentry_test_doesnt_support_streaming__ = true;
10+
11+
export default handler;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const assert = require('assert');
2+
const { getAsync } = require('../utils/server');
3+
4+
// This test asserts that our wrapping of `res.end` doesn't break API routes on Vercel if people call `res.json` or
5+
// `res.send` multiple times in one request handler.
6+
// https://github.com/getsentry/sentry-javascript/issues/6670
7+
module.exports = async ({ url: urlBase }) => {
8+
const response = await getAsync(`${urlBase}/api/doubleEndMethodOnVercel`);
9+
assert.equal(response, '{"success":true}');
10+
};

packages/replay/.eslintrc.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@ module.exports = {
2525
rules: {
2626
// TODO (high-prio): Re-enable this after migration
2727
'@typescript-eslint/explicit-member-accessibility': 'off',
28-
// TODO (high-prio): Re-enable this after migration
28+
// Since we target only es6 here, we can leave this off
2929
'@sentry-internal/sdk/no-async-await': 'off',
30-
// TODO (medium-prio): Re-enable this after migration
31-
'jsdoc/require-jsdoc': 'off',
3230
},
3331
},
3432
{

packages/replay/src/coreHandlers/breadcrumbHandler.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type { InstrumentationTypeBreadcrumb } from '../types';
44
import { DomHandlerData, handleDom } from './handleDom';
55
import { handleScope } from './handleScope';
66

7+
/**
8+
* An event handler to react to breadcrumbs.
9+
*/
710
export function breadcrumbHandler(type: InstrumentationTypeBreadcrumb, handlerData: unknown): Breadcrumb | null {
811
if (type === 'scope') {
912
return handleScope(handlerData as Scope);

packages/replay/src/coreHandlers/handleDom.ts

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export interface DomHandlerData {
99
event: Node | { target: Node };
1010
}
1111

12+
/**
13+
* An event handler to react to DOM events.
14+
*/
1215
export function handleDom(handlerData: DomHandlerData): Breadcrumb | null {
1316
// Taken from https://github.com/getsentry/sentry-javascript/blob/master/packages/browser/src/integrations/breadcrumbs.ts#L112
1417
let target;

packages/replay/src/coreHandlers/handleFetch.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { ReplayPerformanceEntry } from '../createPerformanceEntry';
2-
import type { ReplayContainer } from '../types';
1+
import type { ReplayContainer, ReplayPerformanceEntry } from '../types';
32
import { createPerformanceSpans } from '../util/createPerformanceSpans';
43
import { shouldFilterRequest } from '../util/shouldFilterRequest';
54

packages/replay/src/coreHandlers/handleHistory.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ReplayPerformanceEntry } from '../createPerformanceEntry';
2-
import type { ReplayContainer } from '../types';
1+
import type { ReplayContainer, ReplayPerformanceEntry } from '../types';
32
import { createPerformanceSpans } from '../util/createPerformanceSpans';
43

54
interface HistoryHandlerData {

packages/replay/src/coreHandlers/handleScope.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { createBreadcrumb } from '../util/createBreadcrumb';
44

55
let _LAST_BREADCRUMB: null | Breadcrumb = null;
66

7+
/**
8+
* An event handler to handle scope changes.
9+
*/
710
export function handleScope(scope: Scope): Breadcrumb | null {
811
const newBreadcrumb = scope.getLastBreadcrumb();
912

packages/replay/src/coreHandlers/handleXhr.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ReplayPerformanceEntry } from '../createPerformanceEntry';
2-
import type { ReplayContainer } from '../types';
1+
import type { ReplayContainer, ReplayPerformanceEntry } from '../types';
32
import { createPerformanceSpans } from '../util/createPerformanceSpans';
43
import { shouldFilterRequest } from '../util/shouldFilterRequest';
54

0 commit comments

Comments
 (0)