@@ -6,21 +6,18 @@ const { inspect } = require('util')
66
77const common = require ( './common' )
88const { restoreOverriddenClientRequest } = require ( './intercept' )
9- const { BatchInterceptor } = require ( '@mswjs/interceptors' )
9+ const { EventEmitter } = require ( 'stream' )
10+ const { gzipSync, brotliCompressSync, deflateSync } = require ( 'zlib' )
1011const {
1112 default : nodeInterceptors ,
1213} = require ( '@mswjs/interceptors/presets/node' )
13- const { EventEmitter } = require ( 'stream' )
14-
1514const SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'
1615let recordingInProgress = false
1716let outputs = [ ]
1817
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 ]
2421
2522function getScope ( options ) {
2623 const { proto, host, port } = common . normalizeRequestOptions ( options )
@@ -226,109 +223,137 @@ function record(recOptions) {
226223 restoreOverriddenClientRequest ( )
227224
228225 // 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+ } )
265234
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
272248
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' )
299252
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+ }
314275
315- debug ( 'finished setting up intercepting' )
276+ await recordResponse ( new Response ( body , response ) )
277+ } )
316278
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' )
323291 }
324- } )
325292
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+ }
332357}
333358
334359// Restore *all* the overridden http/https modules' properties.
@@ -338,7 +363,8 @@ function restore() {
338363 'restoring all the overridden http/https properties' ,
339364 )
340365
341- interceptor . dispose ( )
366+ clientRequestInterceptor . dispose ( )
367+ fetchRequestInterceptor . dispose ( )
342368 restoreOverriddenClientRequest ( )
343369 recordingInProgress = false
344370}
0 commit comments