@@ -34,18 +34,31 @@ interface Server {
34
34
publicDir : string ;
35
35
}
36
36
37
- export interface NextRequest extends http . IncomingMessage {
37
+ export type NextRequest = (
38
+ | http . IncomingMessage // in nextjs versions < 12.0.9, `NextRequest` extends `http.IncomingMessage`
39
+ | {
40
+ _req : http . IncomingMessage ; // in nextjs versions >= 12.0.9, `NextRequest` wraps `http.IncomingMessage`
41
+ }
42
+ ) & {
38
43
cookies : Record < string , string > ;
39
44
url : string ;
40
45
query : { [ key : string ] : string } ;
41
46
headers : { [ key : string ] : string } ;
42
47
body : string | { [ key : string ] : unknown } ;
43
- }
44
- type NextResponse = http . ServerResponse ;
48
+ method : string ;
49
+ } ;
50
+
51
+ type NextResponse =
52
+ // in nextjs versions < 12.0.9, `NextResponse` extends `http.ServerResponse`
53
+ | http . ServerResponse
54
+ // in nextjs versions >= 12.0.9, `NextResponse` wraps `http.ServerResponse`
55
+ | {
56
+ _res : http . ServerResponse ;
57
+ } ;
45
58
46
59
// the methods we'll wrap
47
60
type HandlerGetter = ( ) => Promise < ReqHandler > ;
48
- type ReqHandler = ( req : NextRequest , res : NextResponse , parsedUrl ?: url . UrlWithParsedQuery ) => Promise < void > ;
61
+ type ReqHandler = ( nextReq : NextRequest , nextRes : NextResponse , parsedUrl ?: url . UrlWithParsedQuery ) => Promise < void > ;
49
62
type ErrorLogger = ( err : Error ) => void ;
50
63
type ApiPageEnsurer = ( path : string ) => Promise < void > ;
51
64
type PageComponentFinder = (
@@ -205,10 +218,16 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
205
218
// add transaction start and stop to the normal request handling
206
219
const wrappedReqHandler = async function (
207
220
this : Server ,
208
- req : NextRequest ,
209
- res : NextResponse ,
221
+ nextReq : NextRequest ,
222
+ nextRes : NextResponse ,
210
223
parsedUrl ?: url . UrlWithParsedQuery ,
211
224
) : Promise < void > {
225
+ // Starting with version 12.0.9, nextjs wraps the incoming request in a `NodeNextRequest` object and the outgoing
226
+ // response in a `NodeNextResponse` object before passing them to the handler. (This is necessary here but not in
227
+ // `withSentry` because by the time nextjs passes them to an API handler, it's unwrapped them again.)
228
+ const req = '_req' in nextReq ? nextReq . _req : nextReq ;
229
+ const res = '_res' in nextRes ? nextRes . _res : nextRes ;
230
+
212
231
// wrap everything in a domain in order to prevent scope bleed between requests
213
232
const local = domain . create ( ) ;
214
233
local . add ( req ) ;
@@ -220,23 +239,23 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
220
239
const currentScope = getCurrentHub ( ) . getScope ( ) ;
221
240
222
241
if ( currentScope ) {
223
- currentScope . addEventProcessor ( event => parseRequest ( event , req ) ) ;
242
+ currentScope . addEventProcessor ( event => parseRequest ( event , nextReq ) ) ;
224
243
225
244
// We only want to record page and API requests
226
- if ( hasTracingEnabled ( ) && shouldTraceRequest ( req . url , publicDirFiles ) ) {
245
+ if ( hasTracingEnabled ( ) && shouldTraceRequest ( nextReq . url , publicDirFiles ) ) {
227
246
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
228
247
let traceparentData ;
229
- if ( req . headers && isString ( req . headers [ 'sentry-trace' ] ) ) {
230
- traceparentData = extractTraceparentData ( req . headers [ 'sentry-trace' ] as string ) ;
248
+ if ( nextReq . headers && isString ( nextReq . headers [ 'sentry-trace' ] ) ) {
249
+ traceparentData = extractTraceparentData ( nextReq . headers [ 'sentry-trace' ] as string ) ;
231
250
logger . log ( `[Tracing] Continuing trace ${ traceparentData ?. traceId } .` ) ;
232
251
}
233
252
234
253
// pull off query string, if any
235
- const reqPath = stripUrlQueryAndFragment ( req . url ) ;
254
+ const reqPath = stripUrlQueryAndFragment ( nextReq . url ) ;
236
255
237
256
// requests for pages will only ever be GET requests, so don't bother to include the method in the transaction
238
257
// name; requests to API routes could be GET, POST, PUT, etc, so do include it there
239
- const namePrefix = req . url . startsWith ( '/api' ) ? `${ ( req . method || 'GET' ) . toUpperCase ( ) } ` : '' ;
258
+ const namePrefix = nextReq . url . startsWith ( '/api' ) ? `${ nextReq . method . toUpperCase ( ) } ` : '' ;
240
259
241
260
const transaction = startTransaction (
242
261
{
@@ -245,8 +264,12 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
245
264
metadata : { requestPath : reqPath } ,
246
265
...traceparentData ,
247
266
} ,
248
- // extra context passed to the `tracesSampler`
249
- { request : req } ,
267
+ // Extra context passed to the `tracesSampler` (Note: We're combining `nextReq` and `req` this way in order
268
+ // to not break people's `tracesSampler` functions, even though the format of `nextReq` has changed (see
269
+ // note above re: nextjs 12.0.9). If `nextReq === req` (pre 12.0.9), then spreading `req` is a no-op - we're
270
+ // just spreading the same stuff twice. If `nextReq` contains `req` (12.0.9 and later), then spreading `req`
271
+ // mimics the old format by flattening the data.)
272
+ { request : { ...nextReq , ...req } } ,
250
273
) ;
251
274
252
275
currentScope . setSpan ( transaction ) ;
@@ -270,7 +293,7 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
270
293
}
271
294
}
272
295
273
- return origReqHandler . call ( this , req , res , parsedUrl ) ;
296
+ return origReqHandler . call ( this , nextReq , nextRes , parsedUrl ) ;
274
297
} ) ;
275
298
} ;
276
299
0 commit comments