@mswjs/interceptors 0.17.6 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -47
- package/lib/Interceptor.js +4 -4
- package/lib/Interceptor.js.map +1 -1
- package/lib/RemoteHttpInterceptor.d.ts +15 -0
- package/lib/RemoteHttpInterceptor.js +86 -56
- package/lib/RemoteHttpInterceptor.js.map +1 -1
- package/lib/glossary.d.ts +3 -14
- package/lib/glossary.js.map +1 -1
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/NodeClientRequest.d.ts +13 -5
- package/lib/interceptors/ClientRequest/NodeClientRequest.js +179 -166
- package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.get.js +9 -5
- package/lib/interceptors/ClientRequest/http.get.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.request.js +10 -6
- package/lib/interceptors/ClientRequest/http.request.js.map +1 -1
- package/lib/interceptors/ClientRequest/index.d.ts +2 -5
- package/lib/interceptors/ClientRequest/index.js +2 -13
- package/lib/interceptors/ClientRequest/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/cloneIncomingMessage.js +9 -5
- package/lib/interceptors/ClientRequest/utils/cloneIncomingMessage.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/createRequest.d.ts +6 -0
- package/lib/interceptors/ClientRequest/utils/createRequest.js +52 -0
- package/lib/interceptors/ClientRequest/utils/createRequest.js.map +1 -0
- package/lib/interceptors/ClientRequest/utils/createResponse.d.ts +8 -0
- package/lib/interceptors/ClientRequest/utils/createResponse.js +24 -0
- package/lib/interceptors/ClientRequest/utils/createResponse.js.map +1 -0
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js +1 -1
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/normalizeClientRequestArgs.js +8 -8
- package/lib/interceptors/ClientRequest/utils/normalizeClientRequestArgs.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.d.ts +1 -1
- package/lib/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.js +1 -1
- package/lib/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +6 -10
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +203 -143
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/index.d.ts +2 -2
- package/lib/interceptors/XMLHttpRequest/index.js +2 -2
- package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/utils/concatArrayBuffer.d.ts +4 -0
- package/lib/interceptors/XMLHttpRequest/utils/concatArrayBuffer.js +14 -0
- package/lib/interceptors/XMLHttpRequest/utils/concatArrayBuffer.js.map +1 -0
- package/lib/interceptors/XMLHttpRequest/utils/createResponse.d.ts +2 -0
- package/lib/interceptors/XMLHttpRequest/utils/createResponse.js +14 -0
- package/lib/interceptors/XMLHttpRequest/utils/createResponse.js.map +1 -0
- package/lib/interceptors/fetch/index.js +24 -81
- package/lib/interceptors/fetch/index.js.map +1 -1
- package/lib/utils/AsyncEventEmitter.js +12 -8
- package/lib/utils/AsyncEventEmitter.js.map +1 -1
- package/lib/utils/RequestWithCredentials.d.ts +7 -0
- package/lib/utils/RequestWithCredentials.js +20 -0
- package/lib/utils/RequestWithCredentials.js.map +1 -0
- package/lib/utils/bufferUtils.d.ts +7 -2
- package/lib/utils/bufferUtils.js +10 -6
- package/lib/utils/bufferUtils.js.map +1 -1
- package/lib/utils/parseJson.d.ts +1 -1
- package/lib/utils/toInteractiveRequest.d.ts +7 -0
- package/lib/utils/toInteractiveRequest.js +20 -0
- package/lib/utils/toInteractiveRequest.js.map +1 -0
- package/package.json +3 -2
- package/src/RemoteHttpInterceptor.ts +84 -34
- package/src/glossary.ts +5 -18
- package/src/index.ts +0 -2
- package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +17 -23
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +177 -153
- package/src/interceptors/ClientRequest/index.test.ts +5 -3
- package/src/interceptors/ClientRequest/index.ts +2 -26
- package/src/interceptors/ClientRequest/utils/createRequest.test.ts +61 -0
- package/src/interceptors/ClientRequest/utils/createRequest.ts +32 -0
- package/src/interceptors/ClientRequest/utils/createResponse.test.ts +24 -0
- package/src/interceptors/ClientRequest/utils/createResponse.ts +22 -0
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.ts +1 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +234 -174
- package/src/interceptors/XMLHttpRequest/index.ts +3 -2
- package/src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts +12 -0
- package/src/interceptors/XMLHttpRequest/utils/concateArrayBuffer.test.ts +14 -0
- package/src/interceptors/XMLHttpRequest/utils/createResponse.ts +13 -0
- package/src/interceptors/fetch/index.ts +30 -69
- package/src/utils/RequestWithCredentials.ts +21 -0
- package/src/utils/bufferUtils.ts +10 -5
- package/src/utils/parseJson.ts +1 -1
- package/src/utils/toInteractiveRequest.ts +29 -0
- package/lib/InteractiveIsomorphicRequest.d.ts +0 -7
- package/lib/InteractiveIsomorphicRequest.js +0 -37
- package/lib/InteractiveIsomorphicRequest.js.map +0 -1
- package/lib/IsomorphicRequest.d.ts +0 -24
- package/lib/IsomorphicRequest.js +0 -107
- package/lib/IsomorphicRequest.js.map +0 -1
- package/lib/interceptors/ClientRequest/utils/bodyBufferToString.d.ts +0 -2
- package/lib/interceptors/ClientRequest/utils/bodyBufferToString.js +0 -11
- package/lib/interceptors/ClientRequest/utils/bodyBufferToString.js.map +0 -1
- package/lib/interceptors/ClientRequest/utils/concatChunkToBuffer.d.ts +0 -2
- package/lib/interceptors/ClientRequest/utils/concatChunkToBuffer.js +0 -11
- package/lib/interceptors/ClientRequest/utils/concatChunkToBuffer.js.map +0 -1
- package/lib/interceptors/XMLHttpRequest/utils/bufferFrom.d.ts +0 -5
- package/lib/interceptors/XMLHttpRequest/utils/bufferFrom.js +0 -20
- package/lib/interceptors/XMLHttpRequest/utils/bufferFrom.js.map +0 -1
- package/lib/utils/toIsoResponse.d.ts +0 -5
- package/lib/utils/toIsoResponse.js +0 -18
- package/lib/utils/toIsoResponse.js.map +0 -1
- package/src/InteractiveIsomorphicRequest.ts +0 -24
- package/src/IsomorphicRequest.test.ts +0 -106
- package/src/IsomorphicRequest.ts +0 -86
- package/src/interceptors/ClientRequest/utils/bodyBufferToString.test.ts +0 -16
- package/src/interceptors/ClientRequest/utils/bodyBufferToString.ts +0 -7
- package/src/interceptors/ClientRequest/utils/concatChunkToBuffer.test.ts +0 -13
- package/src/interceptors/ClientRequest/utils/concatChunkToBuffer.ts +0 -10
- package/src/interceptors/XMLHttpRequest/utils/bufferFrom.test.ts +0 -11
- package/src/interceptors/XMLHttpRequest/utils/bufferFrom.ts +0 -16
- package/src/utils/toIsoResponse.test.ts +0 -39
- package/src/utils/toIsoResponse.ts +0 -14
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Debugger } from 'debug'
|
|
6
6
|
import { until } from '@open-draft/until'
|
|
7
|
-
import {
|
|
8
|
-
Headers,
|
|
9
|
-
stringToHeaders,
|
|
10
|
-
objectToHeaders,
|
|
11
|
-
headersToString,
|
|
12
|
-
} from 'headers-polyfill'
|
|
7
|
+
import { Headers, stringToHeaders, headersToString } from 'headers-polyfill'
|
|
13
8
|
import { DOMParser } from '@xmldom/xmldom'
|
|
14
9
|
import { parseJson } from '../../utils/parseJson'
|
|
15
|
-
import { toIsoResponse } from '../../utils/toIsoResponse'
|
|
16
|
-
import { bufferFrom } from './utils/bufferFrom'
|
|
17
10
|
import { createEvent } from './utils/createEvent'
|
|
18
11
|
import type { XMLHttpRequestEmitter } from '.'
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
import {
|
|
13
|
+
encodeBuffer,
|
|
14
|
+
decodeBuffer,
|
|
15
|
+
toArrayBuffer,
|
|
16
|
+
} from '../../utils/bufferUtils'
|
|
17
|
+
import { createResponse } from './utils/createResponse'
|
|
18
|
+
import { concatArrayBuffer } from './utils/concatArrayBuffer'
|
|
19
|
+
import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
20
|
+
import { uuidv4 } from '../../utils/uuid'
|
|
21
|
+
import { createRequestWithCredentials } from '../../utils/RequestWithCredentials'
|
|
22
22
|
|
|
23
23
|
type XMLHttpRequestEventHandler = (
|
|
24
24
|
this: XMLHttpRequest,
|
|
@@ -55,6 +55,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
55
55
|
return class XMLHttpRequestOverride implements XMLHttpRequest {
|
|
56
56
|
_requestHeaders: Headers
|
|
57
57
|
_responseHeaders: Headers
|
|
58
|
+
_responseBuffer: Uint8Array
|
|
58
59
|
|
|
59
60
|
// Collection of events modified by `addEventListener`/`removeEventListener` calls.
|
|
60
61
|
_events: XMLHttpRequestEvent<InternalXMLHttpRequestEventTargetEventMap>[] =
|
|
@@ -85,10 +86,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
85
86
|
public user?: string
|
|
86
87
|
public password?: string
|
|
87
88
|
public async?: boolean
|
|
88
|
-
public response: any
|
|
89
|
-
public responseText: string
|
|
90
89
|
public responseType: XMLHttpRequestResponseType
|
|
91
|
-
public responseXML: Document | null
|
|
92
90
|
public responseURL: string
|
|
93
91
|
public upload: XMLHttpRequestUpload
|
|
94
92
|
public readyState: number
|
|
@@ -129,17 +127,15 @@ export const createXMLHttpRequestOverride = (
|
|
|
129
127
|
this.method = 'GET'
|
|
130
128
|
this.readyState = this.UNSENT
|
|
131
129
|
this.withCredentials = false
|
|
132
|
-
this.status =
|
|
133
|
-
this.statusText = '
|
|
134
|
-
this.response = ''
|
|
130
|
+
this.status = 0
|
|
131
|
+
this.statusText = ''
|
|
135
132
|
this.responseType = 'text'
|
|
136
|
-
this.responseText = ''
|
|
137
|
-
this.responseXML = null
|
|
138
133
|
this.responseURL = ''
|
|
139
134
|
this.upload = {} as any
|
|
140
135
|
this.timeout = 0
|
|
141
136
|
|
|
142
137
|
this._requestHeaders = new Headers()
|
|
138
|
+
this._responseBuffer = new Uint8Array()
|
|
143
139
|
this._responseHeaders = new Headers()
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -169,7 +165,6 @@ export const createXMLHttpRequestOverride = (
|
|
|
169
165
|
this.log('trigger "%s" (%d)', eventName, this.readyState)
|
|
170
166
|
this.log('resolve listener for event "%s"', eventName)
|
|
171
167
|
|
|
172
|
-
// @ts-expect-error XMLHttpRequest class has no index signature.
|
|
173
168
|
const callback = this[`on${eventName}`] as XMLHttpRequestEventHandler
|
|
174
169
|
callback?.call(this, createEvent(this, eventName, options))
|
|
175
170
|
|
|
@@ -191,12 +186,10 @@ export const createXMLHttpRequestOverride = (
|
|
|
191
186
|
this.log('reset')
|
|
192
187
|
|
|
193
188
|
this.setReadyState(this.UNSENT)
|
|
194
|
-
this.status =
|
|
195
|
-
this.statusText = '
|
|
196
|
-
this.response = null as any
|
|
197
|
-
this.responseText = null as any
|
|
198
|
-
this.responseXML = null as any
|
|
189
|
+
this.status = 0
|
|
190
|
+
this.statusText = ''
|
|
199
191
|
|
|
192
|
+
this._responseBuffer = new Uint8Array()
|
|
200
193
|
this._requestHeaders = new Headers()
|
|
201
194
|
this._responseHeaders = new Headers()
|
|
202
195
|
}
|
|
@@ -228,12 +221,9 @@ export const createXMLHttpRequestOverride = (
|
|
|
228
221
|
|
|
229
222
|
public send(data?: string | ArrayBuffer) {
|
|
230
223
|
this.log('send %s %s', this.method, this.url)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
} else {
|
|
235
|
-
buffer = data || new ArrayBuffer(0)
|
|
236
|
-
}
|
|
224
|
+
|
|
225
|
+
const requestBuffer: ArrayBuffer | undefined =
|
|
226
|
+
typeof data === 'string' ? encodeBuffer(data) : data
|
|
237
227
|
|
|
238
228
|
let url: URL
|
|
239
229
|
|
|
@@ -249,34 +239,36 @@ export const createXMLHttpRequestOverride = (
|
|
|
249
239
|
this.log('request headers', this._requestHeaders)
|
|
250
240
|
|
|
251
241
|
// Create an intercepted request instance exposed to the request intercepting middleware.
|
|
252
|
-
const
|
|
253
|
-
|
|
242
|
+
const requestId = uuidv4()
|
|
243
|
+
const capturedRequest = createRequestWithCredentials(url, {
|
|
254
244
|
method: this.method,
|
|
255
245
|
headers: this._requestHeaders,
|
|
256
246
|
credentials: this.withCredentials ? 'include' : 'omit',
|
|
247
|
+
body: requestBuffer,
|
|
257
248
|
})
|
|
258
249
|
|
|
259
|
-
const
|
|
260
|
-
isomorphicRequest
|
|
261
|
-
)
|
|
250
|
+
const interactiveRequest = toInteractiveRequest(capturedRequest)
|
|
262
251
|
|
|
263
252
|
this.log(
|
|
264
253
|
'emitting the "request" event for %d listener(s)...',
|
|
265
254
|
emitter.listenerCount('request')
|
|
266
255
|
)
|
|
267
|
-
emitter.emit('request',
|
|
256
|
+
emitter.emit('request', interactiveRequest, requestId)
|
|
268
257
|
|
|
269
258
|
this.log('awaiting mocked response...')
|
|
270
259
|
|
|
271
260
|
Promise.resolve(
|
|
272
261
|
until(async () => {
|
|
273
|
-
await emitter.untilIdle(
|
|
274
|
-
|
|
275
|
-
|
|
262
|
+
await emitter.untilIdle(
|
|
263
|
+
'request',
|
|
264
|
+
({ args: [, pendingRequestId] }) => {
|
|
265
|
+
return pendingRequestId === requestId
|
|
266
|
+
}
|
|
267
|
+
)
|
|
276
268
|
this.log('all request listeners have been resolved!')
|
|
277
269
|
|
|
278
270
|
const [mockedResponse] =
|
|
279
|
-
await
|
|
271
|
+
await interactiveRequest.respondWith.invoked()
|
|
280
272
|
this.log('event.respondWith called with:', mockedResponse)
|
|
281
273
|
|
|
282
274
|
return mockedResponse
|
|
@@ -290,72 +282,108 @@ export const createXMLHttpRequestOverride = (
|
|
|
290
282
|
middlewareException
|
|
291
283
|
)
|
|
292
284
|
|
|
285
|
+
// Mark the request as complete.
|
|
286
|
+
this.setReadyState(this.DONE)
|
|
287
|
+
|
|
293
288
|
// No way to propagate the actual error message.
|
|
294
289
|
this.trigger('error')
|
|
295
|
-
|
|
290
|
+
|
|
291
|
+
// Emit the "loadend" event to notify that the request has settled.
|
|
292
|
+
// In this case, there's been an error with the request so
|
|
293
|
+
// we must not emit the "load" event.
|
|
294
|
+
this.trigger('loadend')
|
|
295
|
+
|
|
296
|
+
// Abort must not be called when request fails!
|
|
297
|
+
// this.abort()
|
|
296
298
|
|
|
297
299
|
return
|
|
298
300
|
}
|
|
299
301
|
|
|
302
|
+
// Forward request headers modified in the "request" listener.
|
|
303
|
+
this._requestHeaders = new Headers(capturedRequest.headers)
|
|
304
|
+
|
|
300
305
|
// Return a mocked response, if provided in the middleware.
|
|
301
306
|
if (mockedResponse) {
|
|
307
|
+
const responseClone = mockedResponse.clone()
|
|
302
308
|
this.log('received mocked response', mockedResponse)
|
|
303
309
|
|
|
304
|
-
// Trigger a loadstart event to indicate the initialization of the fetch.
|
|
305
|
-
this.trigger('loadstart')
|
|
306
|
-
|
|
307
310
|
this.status = mockedResponse.status ?? 200
|
|
308
311
|
this.statusText = mockedResponse.statusText || 'OK'
|
|
309
|
-
this._responseHeaders = mockedResponse.headers
|
|
310
|
-
? objectToHeaders(mockedResponse.headers)
|
|
311
|
-
: new Headers()
|
|
312
|
-
|
|
313
312
|
this.log('set response status', this.status, this.statusText)
|
|
313
|
+
|
|
314
|
+
this._responseHeaders = new Headers(mockedResponse.headers || {})
|
|
314
315
|
this.log('set response headers', this._responseHeaders)
|
|
315
316
|
|
|
317
|
+
this.log('response type', this.responseType)
|
|
318
|
+
this.responseURL = this.url
|
|
319
|
+
|
|
320
|
+
const totalLength = this._responseHeaders.has('Content-Length')
|
|
321
|
+
? Number(this._responseHeaders.get('Content-Length'))
|
|
322
|
+
: undefined
|
|
323
|
+
|
|
324
|
+
// Trigger a loadstart event to indicate the initialization of the fetch.
|
|
325
|
+
this.trigger('loadstart', { loaded: 0, total: totalLength })
|
|
326
|
+
|
|
316
327
|
// Mark that response headers has been received
|
|
317
328
|
// and trigger a ready state event to reflect received headers
|
|
318
|
-
// in a custom
|
|
329
|
+
// in a custom "onreadystatechange" callback.
|
|
319
330
|
this.setReadyState(this.HEADERS_RECEIVED)
|
|
320
331
|
|
|
321
|
-
this.
|
|
322
|
-
this.response = this.getResponseBody(mockedResponse.body)
|
|
323
|
-
this.responseURL = this.url
|
|
324
|
-
this.responseText = mockedResponse.body || ''
|
|
325
|
-
this.responseXML = this.getResponseXML()
|
|
332
|
+
this.setReadyState(this.LOADING)
|
|
326
333
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
334
|
+
const closeResponseStream = () => {
|
|
335
|
+
/**
|
|
336
|
+
* Explicitly mark the request as done so its response never hangs.
|
|
337
|
+
* @see https://github.com/mswjs/interceptors/issues/13
|
|
338
|
+
*/
|
|
339
|
+
this.setReadyState(this.DONE)
|
|
331
340
|
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
341
|
+
// Always trigger the "load" event because at this point
|
|
342
|
+
// the request has been performed successfully.
|
|
343
|
+
this.trigger('load', {
|
|
344
|
+
loaded: this._responseBuffer.byteLength,
|
|
345
|
+
total: totalLength,
|
|
346
|
+
})
|
|
335
347
|
|
|
336
|
-
// Trigger a
|
|
337
|
-
this.trigger('
|
|
338
|
-
loaded:
|
|
339
|
-
total:
|
|
348
|
+
// Trigger a loadend event to indicate the fetch has completed.
|
|
349
|
+
this.trigger('loadend', {
|
|
350
|
+
loaded: this._responseBuffer.byteLength,
|
|
351
|
+
total: totalLength,
|
|
340
352
|
})
|
|
353
|
+
|
|
354
|
+
emitter.emit('response', responseClone, capturedRequest, requestId)
|
|
341
355
|
}
|
|
342
356
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
* @see https://github.com/mswjs/interceptors/issues/13
|
|
346
|
-
*/
|
|
347
|
-
this.setReadyState(this.DONE)
|
|
357
|
+
if (mockedResponse.body) {
|
|
358
|
+
const reader = mockedResponse.body.getReader()
|
|
348
359
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// Trigger a loadend event to indicate the fetch has completed.
|
|
352
|
-
this.trigger('loadend')
|
|
360
|
+
const readNextChunk = async (): Promise<void> => {
|
|
361
|
+
const { value, done } = await reader.read()
|
|
353
362
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
363
|
+
if (done) {
|
|
364
|
+
closeResponseStream()
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (value) {
|
|
369
|
+
this._responseBuffer = concatArrayBuffer(
|
|
370
|
+
this._responseBuffer,
|
|
371
|
+
value
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
this.trigger('progress', {
|
|
375
|
+
loaded: this._responseBuffer.byteLength,
|
|
376
|
+
total: totalLength,
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
readNextChunk()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
readNextChunk()
|
|
384
|
+
} else {
|
|
385
|
+
closeResponseStream()
|
|
386
|
+
}
|
|
359
387
|
} else {
|
|
360
388
|
this.log('no mocked response received!')
|
|
361
389
|
|
|
@@ -371,72 +399,148 @@ export const createXMLHttpRequestOverride = (
|
|
|
371
399
|
this.password
|
|
372
400
|
)
|
|
373
401
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
402
|
+
originalRequest.addEventListener('readystatechange', () => {
|
|
403
|
+
// Forward the original response headers to the patched instance
|
|
404
|
+
// immediately as they are received.
|
|
405
|
+
if (
|
|
406
|
+
originalRequest.readyState === XMLHttpRequest.HEADERS_RECEIVED
|
|
407
|
+
) {
|
|
408
|
+
const responseHeaders = originalRequest.getAllResponseHeaders()
|
|
409
|
+
this.log('original response headers:\n', responseHeaders)
|
|
410
|
+
|
|
411
|
+
this._responseHeaders = stringToHeaders(responseHeaders)
|
|
412
|
+
this.log(
|
|
413
|
+
'original response headers (normalized)',
|
|
414
|
+
this._responseHeaders
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
originalRequest.addEventListener('loadstart', () => {
|
|
420
|
+
// Forward the response type to the patched instance immediately.
|
|
421
|
+
// Response type affects how response reading properties are resolved.
|
|
422
|
+
this.responseType = originalRequest.responseType
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
originalRequest.addEventListener('progress', () => {
|
|
426
|
+
this._responseBuffer = concatArrayBuffer(
|
|
427
|
+
this._responseBuffer,
|
|
428
|
+
encodeBuffer(originalRequest.responseText)
|
|
429
|
+
)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// Update the patched instance on the "loadend" event
|
|
433
|
+
// because it fires when the request settles (succeeds/errors).
|
|
434
|
+
originalRequest.addEventListener('loadend', () => {
|
|
435
|
+
this.log('original "loadend"')
|
|
378
436
|
|
|
379
437
|
this.status = originalRequest.status
|
|
380
438
|
this.statusText = originalRequest.statusText
|
|
381
439
|
this.responseURL = originalRequest.responseURL
|
|
382
|
-
this.
|
|
383
|
-
this.response = originalRequest.response
|
|
384
|
-
this.responseText = originalRequest.responseText
|
|
385
|
-
this.responseXML = originalRequest.responseXML
|
|
386
|
-
|
|
387
|
-
this.log('set mock request readyState to DONE')
|
|
440
|
+
this.log('received original response', this.status, this.statusText)
|
|
388
441
|
|
|
389
442
|
// Explicitly mark the mocked request instance as done
|
|
390
443
|
// so the response never hangs.
|
|
391
|
-
/**
|
|
392
|
-
* @note `readystatechange` listener is called TWICE
|
|
393
|
-
* in the case of unhandled request.
|
|
394
|
-
*/
|
|
395
444
|
this.setReadyState(this.DONE)
|
|
445
|
+
this.log('set mock request readyState to DONE')
|
|
396
446
|
|
|
397
|
-
this.log('received original response', this.status, this.statusText)
|
|
398
447
|
this.log('original response body:', this.response)
|
|
448
|
+
this.log('original response finished!')
|
|
399
449
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
'original response headers (normalized)',
|
|
406
|
-
this._responseHeaders
|
|
450
|
+
emitter.emit(
|
|
451
|
+
'response',
|
|
452
|
+
createResponse(originalRequest, this._responseBuffer),
|
|
453
|
+
capturedRequest,
|
|
454
|
+
requestId
|
|
407
455
|
)
|
|
408
|
-
|
|
409
|
-
this.log('original response finished')
|
|
410
|
-
|
|
411
|
-
emitter.emit('response', isomorphicRequest, {
|
|
412
|
-
status: originalRequest.status,
|
|
413
|
-
statusText: originalRequest.statusText,
|
|
414
|
-
headers: this._responseHeaders,
|
|
415
|
-
body: originalRequest.response,
|
|
416
|
-
})
|
|
417
456
|
})
|
|
418
457
|
|
|
458
|
+
this.propagateHeaders(originalRequest, this._requestHeaders)
|
|
459
|
+
|
|
419
460
|
// Assign callbacks and event listeners from the intercepted XHR instance
|
|
420
461
|
// to the original XHR instance.
|
|
421
462
|
this.propagateCallbacks(originalRequest)
|
|
422
463
|
this.propagateListeners(originalRequest)
|
|
423
|
-
this.propagateHeaders(originalRequest, this._requestHeaders)
|
|
424
464
|
|
|
425
465
|
if (this.async) {
|
|
426
466
|
originalRequest.timeout = this.timeout
|
|
427
467
|
}
|
|
428
468
|
|
|
469
|
+
/**
|
|
470
|
+
* @note Set the intercepted request ID on the original request
|
|
471
|
+
* so that if it triggers any other interceptors, they don't attempt
|
|
472
|
+
* to process it once again. This happens when bypassing XMLHttpRequest
|
|
473
|
+
* because it's polyfilled with "http.ClientRequest" in JSDOM.
|
|
474
|
+
*/
|
|
475
|
+
originalRequest.setRequestHeader('X-Request-Id', requestId)
|
|
476
|
+
|
|
429
477
|
this.log('send', data)
|
|
430
478
|
originalRequest.send(data)
|
|
431
479
|
}
|
|
432
480
|
})
|
|
433
481
|
}
|
|
434
482
|
|
|
483
|
+
public get responseText(): string {
|
|
484
|
+
this.log('responseText()')
|
|
485
|
+
|
|
486
|
+
const encoding = this.getResponseHeader('Content-Encoding') as
|
|
487
|
+
| BufferEncoding
|
|
488
|
+
| undefined
|
|
489
|
+
|
|
490
|
+
return decodeBuffer(this._responseBuffer, encoding || undefined)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
public get response(): unknown {
|
|
494
|
+
switch (this.responseType) {
|
|
495
|
+
case 'json': {
|
|
496
|
+
this.log('resolving response body as JSON')
|
|
497
|
+
return parseJson(this.responseText)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
case 'arraybuffer': {
|
|
501
|
+
this.log('resolving response body as ArrayBuffer')
|
|
502
|
+
return toArrayBuffer(this._responseBuffer)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
case 'blob': {
|
|
506
|
+
const mimeType =
|
|
507
|
+
this.getResponseHeader('content-type') || 'text/plain'
|
|
508
|
+
this.log('resolving response body as blog (%s)', mimeType)
|
|
509
|
+
return new Blob([this.responseText], { type: mimeType })
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
case 'document': {
|
|
513
|
+
this.log('resolving response body as XML')
|
|
514
|
+
return this.responseXML
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
default: {
|
|
518
|
+
return this.responseText
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
public get responseXML(): Document | null {
|
|
524
|
+
const contentType = this.getResponseHeader('content-type') || ''
|
|
525
|
+
this.log('responseXML() %s', contentType)
|
|
526
|
+
|
|
527
|
+
if (
|
|
528
|
+
contentType.startsWith('application/xml') ||
|
|
529
|
+
contentType.startsWith('text/xml')
|
|
530
|
+
) {
|
|
531
|
+
this.log('response content-type is XML, parsing...')
|
|
532
|
+
return new DOMParser().parseFromString(this.responseText, contentType)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
this.log('response content type is not XML, returning null...')
|
|
536
|
+
return null
|
|
537
|
+
}
|
|
538
|
+
|
|
435
539
|
public abort() {
|
|
436
|
-
this.log('abort')
|
|
540
|
+
this.log('abort()')
|
|
437
541
|
|
|
438
542
|
if (this.readyState > this.UNSENT && this.readyState < this.DONE) {
|
|
439
|
-
this.
|
|
543
|
+
this.reset()
|
|
440
544
|
this.trigger('abort')
|
|
441
545
|
}
|
|
442
546
|
}
|
|
@@ -446,12 +550,12 @@ export const createXMLHttpRequestOverride = (
|
|
|
446
550
|
}
|
|
447
551
|
|
|
448
552
|
public setRequestHeader(name: string, value: string) {
|
|
449
|
-
this.log('
|
|
553
|
+
this.log('setRequestHeader() "%s" to "%s"', name, value)
|
|
450
554
|
this._requestHeaders.append(name, value)
|
|
451
555
|
}
|
|
452
556
|
|
|
453
557
|
public getResponseHeader(name: string): string | null {
|
|
454
|
-
this.log('
|
|
558
|
+
this.log('getResponseHeader() "%s"', name)
|
|
455
559
|
|
|
456
560
|
if (this.readyState < this.HEADERS_RECEIVED) {
|
|
457
561
|
this.log(
|
|
@@ -474,7 +578,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
474
578
|
}
|
|
475
579
|
|
|
476
580
|
public getAllResponseHeaders(): string {
|
|
477
|
-
this.log('
|
|
581
|
+
this.log('getAllResponseHeaders()')
|
|
478
582
|
|
|
479
583
|
if (this.readyState < this.HEADERS_RECEIVED) {
|
|
480
584
|
this.log(
|
|
@@ -488,70 +592,27 @@ export const createXMLHttpRequestOverride = (
|
|
|
488
592
|
}
|
|
489
593
|
|
|
490
594
|
public addEventListener<
|
|
491
|
-
|
|
492
|
-
>(
|
|
493
|
-
this.log('addEventListener',
|
|
595
|
+
Event extends keyof InternalXMLHttpRequestEventTargetEventMap
|
|
596
|
+
>(event: Event, listener: XMLHttpRequestEventHandler) {
|
|
597
|
+
this.log('addEventListener', event, listener)
|
|
494
598
|
this._events.push({
|
|
495
|
-
name,
|
|
599
|
+
name: event,
|
|
496
600
|
listener,
|
|
497
601
|
})
|
|
498
602
|
}
|
|
499
603
|
|
|
500
|
-
public removeEventListener<
|
|
501
|
-
|
|
502
|
-
listener: (event?: XMLHttpRequestEventMap[
|
|
604
|
+
public removeEventListener<Event extends keyof XMLHttpRequestEventMap>(
|
|
605
|
+
event: Event,
|
|
606
|
+
listener: (event?: XMLHttpRequestEventMap[Event]) => void
|
|
503
607
|
): void {
|
|
504
608
|
this.log('removeEventListener', name, listener)
|
|
505
609
|
this._events = this._events.filter((storedEvent) => {
|
|
506
|
-
return storedEvent.name !==
|
|
610
|
+
return storedEvent.name !== event && storedEvent.listener !== listener
|
|
507
611
|
})
|
|
508
612
|
}
|
|
509
613
|
|
|
510
614
|
public overrideMimeType() {}
|
|
511
615
|
|
|
512
|
-
/**
|
|
513
|
-
* Resolves the response based on the `responseType` value.
|
|
514
|
-
*/
|
|
515
|
-
getResponseBody(body: string | undefined) {
|
|
516
|
-
// Handle an improperly set "null" value of the mocked response body.
|
|
517
|
-
const textBody = body ?? ''
|
|
518
|
-
this.log('coerced response body to', textBody)
|
|
519
|
-
|
|
520
|
-
switch (this.responseType) {
|
|
521
|
-
case 'json': {
|
|
522
|
-
this.log('resolving response body as JSON')
|
|
523
|
-
return parseJson(textBody)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
case 'blob': {
|
|
527
|
-
const blobType =
|
|
528
|
-
this.getResponseHeader('content-type') || 'text/plain'
|
|
529
|
-
this.log('resolving response body as Blob', { type: blobType })
|
|
530
|
-
|
|
531
|
-
return new Blob([textBody], {
|
|
532
|
-
type: blobType,
|
|
533
|
-
})
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
case 'arraybuffer': {
|
|
537
|
-
this.log('resolving response body as ArrayBuffer')
|
|
538
|
-
const arrayBuffer = bufferFrom(textBody)
|
|
539
|
-
return arrayBuffer
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
default:
|
|
543
|
-
return textBody
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
getResponseXML() {
|
|
548
|
-
const contentType = this.getResponseHeader('Content-Type')
|
|
549
|
-
if (contentType === 'application/xml' || contentType === 'text/xml') {
|
|
550
|
-
return new DOMParser().parseFromString(this.responseText, contentType)
|
|
551
|
-
}
|
|
552
|
-
return null
|
|
553
|
-
}
|
|
554
|
-
|
|
555
616
|
/**
|
|
556
617
|
* Propagates mock XMLHttpRequest instance callbacks
|
|
557
618
|
* to the given XMLHttpRequest instance.
|
|
@@ -608,15 +669,14 @@ export const createXMLHttpRequestOverride = (
|
|
|
608
669
|
propagateHeaders(request: XMLHttpRequest, headers: Headers) {
|
|
609
670
|
this.log('propagating request headers to the original request', headers)
|
|
610
671
|
|
|
611
|
-
|
|
612
|
-
Object.entries(headers.raw()).forEach(([name, value]) => {
|
|
672
|
+
for (const [headerName, headerValue] of headers) {
|
|
613
673
|
this.log(
|
|
614
674
|
'setting "%s" (%s) header on the original request',
|
|
615
|
-
|
|
616
|
-
|
|
675
|
+
headerName,
|
|
676
|
+
headerValue
|
|
617
677
|
)
|
|
618
|
-
request.setRequestHeader(
|
|
619
|
-
}
|
|
678
|
+
request.setRequestHeader(headerName, headerValue)
|
|
679
|
+
}
|
|
620
680
|
}
|
|
621
681
|
}
|
|
622
682
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { invariant } from 'outvariant'
|
|
2
2
|
import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
|
|
3
|
-
import {
|
|
3
|
+
import { InteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
4
4
|
import { Interceptor } from '../../Interceptor'
|
|
5
5
|
import { AsyncEventEmitter } from '../../utils/AsyncEventEmitter'
|
|
6
6
|
import { createXMLHttpRequestOverride } from './XMLHttpRequestOverride'
|
|
7
7
|
|
|
8
8
|
export type XMLHttpRequestEventListener = (
|
|
9
|
-
request:
|
|
9
|
+
request: InteractiveRequest,
|
|
10
|
+
requestId: string
|
|
10
11
|
) => Promise<void> | void
|
|
11
12
|
|
|
12
13
|
export type XMLHttpRequestEmitter = AsyncEventEmitter<HttpRequestEventMap>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concatenate two `Uint8Array` buffers.
|
|
3
|
+
*/
|
|
4
|
+
export function concatArrayBuffer(
|
|
5
|
+
left: Uint8Array,
|
|
6
|
+
right: Uint8Array
|
|
7
|
+
): Uint8Array {
|
|
8
|
+
const result = new Uint8Array(left.byteLength + right.byteLength)
|
|
9
|
+
result.set(left, 0)
|
|
10
|
+
result.set(right, left.byteLength)
|
|
11
|
+
return result
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import { concatArrayBuffer } from './concatArrayBuffer'
|
|
5
|
+
|
|
6
|
+
const encoder = new TextEncoder()
|
|
7
|
+
|
|
8
|
+
it('concatenates two Uint8Array buffers', () => {
|
|
9
|
+
const result = concatArrayBuffer(
|
|
10
|
+
encoder.encode('hello'),
|
|
11
|
+
encoder.encode('world')
|
|
12
|
+
)
|
|
13
|
+
expect(result).toEqual(encoder.encode('helloworld'))
|
|
14
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Response } from '@remix-run/web-fetch'
|
|
2
|
+
import { stringToHeaders } from 'headers-polyfill'
|
|
3
|
+
|
|
4
|
+
export function createResponse(
|
|
5
|
+
request: XMLHttpRequest,
|
|
6
|
+
responseBody: Uint8Array
|
|
7
|
+
): Response {
|
|
8
|
+
return new Response(responseBody, {
|
|
9
|
+
status: request.status,
|
|
10
|
+
statusText: request.statusText,
|
|
11
|
+
headers: stringToHeaders(request.getAllResponseHeaders()),
|
|
12
|
+
})
|
|
13
|
+
}
|