@@ -6,21 +6,18 @@ const { inspect } = require('util')
6
6
7
7
const common = require ( './common' )
8
8
const { restoreOverriddenClientRequest } = require ( './intercept' )
9
- const { BatchInterceptor } = require ( '@mswjs/interceptors' )
9
+ const { EventEmitter } = require ( 'stream' )
10
+ const { gzipSync, brotliCompressSync, deflateSync } = require ( 'zlib' )
10
11
const {
11
12
default : nodeInterceptors ,
12
13
} = require ( '@mswjs/interceptors/presets/node' )
13
- const { EventEmitter } = require ( 'stream' )
14
-
15
14
const SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'
16
15
let recordingInProgress = false
17
16
let outputs = [ ]
18
17
19
- // TODO: Consider use one BatchInterceptor (and not one for intercept and one for record)
20
- const interceptor = new BatchInterceptor ( {
21
- name : 'nock-interceptor' ,
22
- interceptors : nodeInterceptors ,
23
- } )
18
+ // TODO: don't reuse the nodeInterceptors, create new ones.
19
+ const clientRequestInterceptor = nodeInterceptors [ 0 ]
20
+ const fetchRequestInterceptor = nodeInterceptors [ 2 ]
24
21
25
22
function getScope ( options ) {
26
23
const { proto, host, port } = common . normalizeRequestOptions ( options )
@@ -226,109 +223,137 @@ function record(recOptions) {
226
223
restoreOverriddenClientRequest ( )
227
224
228
225
// We override the requests so that we can save information on them before executing.
229
- interceptor . apply ( )
230
- interceptor . on (
231
- 'request' ,
232
- async function ( { request : mswRequest , requestId } ) {
233
- const request = mswRequest . clone ( )
234
- const { options } = common . normalizeClientRequestArgs ( request . url )
235
- options . method = request . method
236
- const proto = options . protocol . slice ( 0 , - 1 )
237
-
238
- // Node 0.11 https.request calls http.request -- don't want to record things
239
- // twice.
240
- /* istanbul ignore if */
241
- if ( options . _recording ) {
242
- return
243
- }
244
- options . _recording = true
245
-
246
- const req = new EventEmitter ( )
247
- req . on ( 'response' , function ( ) {
248
- debug ( thisRecordingId , 'intercepting' , proto , 'request to record' )
249
-
250
- // Intercept "res.once('end', ...)"-like event
251
- interceptor . once (
252
- 'response' ,
253
- async function ( { response : mswResponse } ) {
254
- const response = mswResponse . clone ( )
255
- debug ( thisRecordingId , proto , 'intercepted request ended' )
256
-
257
- let reqheaders
258
- // Ignore request headers completely unless it was explicitly enabled by the user (see README)
259
- if ( enableReqHeadersRecording ) {
260
- // We never record user-agent headers as they are worse than useless -
261
- // they actually make testing more difficult without providing any benefit (see README)
262
- reqheaders = Object . fromEntries ( request . headers . entries ( ) )
263
- common . deleteHeadersField ( reqheaders , 'user-agent' )
264
- }
226
+ clientRequestInterceptor . apply ( )
227
+ fetchRequestInterceptor . apply ( )
228
+ clientRequestInterceptor . on ( 'request' , async function ( { request } ) {
229
+ await recordRequest ( request )
230
+ } )
231
+ fetchRequestInterceptor . on ( 'request' , async function ( { request } ) {
232
+ await recordRequest ( request )
233
+ } )
265
234
266
- const headers = Object . fromEntries ( response . headers . entries ( ) )
267
- const res = {
268
- statusCode : response . status ,
269
- headers,
270
- rawHeaders : headers ,
271
- }
235
+ async function recordRequest ( mswRequest ) {
236
+ const request = mswRequest . clone ( )
237
+ const { options } = common . normalizeClientRequestArgs ( request . url )
238
+ options . method = request . method
239
+ const proto = options . protocol . slice ( 0 , - 1 )
240
+
241
+ // Node 0.11 https.request calls http.request -- don't want to record things
242
+ // twice.
243
+ /* istanbul ignore if */
244
+ if ( options . _recording ) {
245
+ return
246
+ }
247
+ options . _recording = true
272
248
273
- const generateFn = outputObjects
274
- ? generateRequestAndResponseObject
275
- : generateRequestAndResponse
276
- let out = generateFn ( {
277
- req : options ,
278
- bodyChunks : [ Buffer . from ( await request . arrayBuffer ( ) ) ] ,
279
- options,
280
- res,
281
- dataChunks : [ Buffer . from ( await response . arrayBuffer ( ) ) ] ,
282
- reqheaders,
283
- } )
284
-
285
- debug ( 'out:' , out )
286
-
287
- // Check that the request was made during the current recording.
288
- // If it hasn't then skip it. There is no other simple way to handle
289
- // this as it depends on the timing of requests and responses. Throwing
290
- // will make some recordings/unit tests fail randomly depending on how
291
- // fast/slow the response arrived.
292
- // If you are seeing this error then you need to make sure that all
293
- // the requests made during a single recording session finish before
294
- // ending the same recording session.
295
- if ( thisRecordingId !== currentRecordingId ) {
296
- debug ( 'skipping recording of an out-of-order request' , out )
297
- return
298
- }
249
+ const req = new EventEmitter ( )
250
+ req . on ( 'response' , function ( ) {
251
+ debug ( thisRecordingId , 'intercepting' , proto , 'request to record' )
299
252
300
- outputs . push ( out )
301
-
302
- if ( ! dontPrint ) {
303
- if ( useSeparator ) {
304
- if ( typeof out !== 'string' ) {
305
- out = JSON . stringify ( out , null , 2 )
306
- }
307
- logging ( SEPARATOR + out + SEPARATOR )
308
- } else {
309
- logging ( out )
310
- }
311
- }
312
- } ,
313
- )
253
+ clientRequestInterceptor . once ( 'response' , async function ( { response } ) {
254
+ await recordResponse ( response )
255
+ } )
256
+ fetchRequestInterceptor . once ( 'response' , async function ( { response } ) {
257
+ // fetch decompresses the body automatically, so we need to recompress it
258
+ const codings =
259
+ response . headers
260
+ . get ( 'content-encoding' )
261
+ ?. toLowerCase ( )
262
+ . split ( ',' )
263
+ . map ( c => c . trim ( ) ) || [ ]
264
+
265
+ let body = await response . arrayBuffer ( )
266
+ for ( const coding of codings ) {
267
+ if ( coding === 'gzip' ) {
268
+ body = gzipSync ( body )
269
+ } else if ( coding === 'deflate' ) {
270
+ body = deflateSync ( body )
271
+ } else if ( coding === 'br' ) {
272
+ body = brotliCompressSync ( body )
273
+ }
274
+ }
314
275
315
- debug ( 'finished setting up intercepting' )
276
+ await recordResponse ( new Response ( body , response ) )
277
+ } )
316
278
317
- // We override both the http and the https modules; when we are
318
- // serializing the request, we need to know which was called.
319
- // By stuffing the state, we can make sure that nock records
320
- // the intended protocol.
321
- if ( proto === 'https' ) {
322
- options . proto = 'https'
279
+ // Intercept "res.once('end', ...)"-like event
280
+ async function recordResponse ( mswResponse ) {
281
+ const response = mswResponse . clone ( )
282
+ debug ( thisRecordingId , proto , 'intercepted request ended' )
283
+
284
+ let reqheaders
285
+ // Ignore request headers completely unless it was explicitly enabled by the user (see README)
286
+ if ( enableReqHeadersRecording ) {
287
+ // We never record user-agent headers as they are worse than useless -
288
+ // they actually make testing more difficult without providing any benefit (see README)
289
+ reqheaders = Object . fromEntries ( request . headers . entries ( ) )
290
+ common . deleteHeadersField ( reqheaders , 'user-agent' )
323
291
}
324
- } )
325
292
326
- // This is a massive change, we are trying to change minimum code, so we emit end event here
327
- // because mswjs take care for these events
328
- // TODO: refactor the recorder, we no longer need all the listeners and can just record the request we get from MSW
329
- req . emit ( 'response' )
330
- } ,
331
- )
293
+ const headers = Object . fromEntries ( response . headers . entries ( ) )
294
+ const res = {
295
+ statusCode : response . status ,
296
+ headers,
297
+ rawHeaders : headers ,
298
+ }
299
+
300
+ const generateFn = outputObjects
301
+ ? generateRequestAndResponseObject
302
+ : generateRequestAndResponse
303
+ let out = generateFn ( {
304
+ req : options ,
305
+ bodyChunks : [ Buffer . from ( await request . arrayBuffer ( ) ) ] ,
306
+ options,
307
+ res,
308
+ dataChunks : [ Buffer . from ( await response . arrayBuffer ( ) ) ] ,
309
+ reqheaders,
310
+ } )
311
+
312
+ debug ( 'out:' , out )
313
+
314
+ // Check that the request was made during the current recording.
315
+ // If it hasn't then skip it. There is no other simple way to handle
316
+ // this as it depends on the timing of requests and responses. Throwing
317
+ // will make some recordings/unit tests fail randomly depending on how
318
+ // fast/slow the response arrived.
319
+ // If you are seeing this error then you need to make sure that all
320
+ // the requests made during a single recording session finish before
321
+ // ending the same recording session.
322
+ if ( thisRecordingId !== currentRecordingId ) {
323
+ debug ( 'skipping recording of an out-of-order request' , out )
324
+ return
325
+ }
326
+
327
+ outputs . push ( out )
328
+
329
+ if ( ! dontPrint ) {
330
+ if ( useSeparator ) {
331
+ if ( typeof out !== 'string' ) {
332
+ out = JSON . stringify ( out , null , 2 )
333
+ }
334
+ logging ( SEPARATOR + out + SEPARATOR )
335
+ } else {
336
+ logging ( out )
337
+ }
338
+ }
339
+ }
340
+
341
+ debug ( 'finished setting up intercepting' )
342
+
343
+ // We override both the http and the https modules; when we are
344
+ // serializing the request, we need to know which was called.
345
+ // By stuffing the state, we can make sure that nock records
346
+ // the intended protocol.
347
+ if ( proto === 'https' ) {
348
+ options . proto = 'https'
349
+ }
350
+ } )
351
+
352
+ // This is a massive change, we are trying to change minimum code, so we emit end event here
353
+ // because mswjs take care for these events
354
+ // TODO: refactor the recorder, we no longer need all the listeners and can just record the request we get from MSW
355
+ req . emit ( 'response' )
356
+ }
332
357
}
333
358
334
359
// Restore *all* the overridden http/https modules' properties.
@@ -338,7 +363,8 @@ function restore() {
338
363
'restoring all the overridden http/https properties' ,
339
364
)
340
365
341
- interceptor . dispose ( )
366
+ clientRequestInterceptor . dispose ( )
367
+ fetchRequestInterceptor . dispose ( )
342
368
restoreOverriddenClientRequest ( )
343
369
recordingInProgress = false
344
370
}
0 commit comments