@mswjs/interceptors 0.14.1 → 0.15.2
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/LICENSE.md +9 -0
- package/README.md +68 -49
- package/lib/BatchInterceptor.d.ts +18 -0
- package/lib/BatchInterceptor.js +79 -0
- package/lib/BatchInterceptor.js.map +1 -0
- package/lib/Interceptor.d.ts +49 -0
- package/lib/Interceptor.js +197 -0
- package/lib/Interceptor.js.map +1 -0
- package/lib/RemoteInterceptor.d.ts +24 -0
- package/lib/RemoteInterceptor.js +216 -0
- package/lib/RemoteInterceptor.js.map +1 -0
- package/lib/glossary.d.ts +32 -0
- package/lib/glossary.js +3 -0
- package/lib/glossary.js.map +1 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/NodeClientRequest.d.ts +5 -5
- package/lib/interceptors/ClientRequest/NodeClientRequest.js +65 -16
- package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.get.d.ts +2 -3
- package/lib/interceptors/ClientRequest/http.get.js +2 -5
- package/lib/interceptors/ClientRequest/http.get.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.request.d.ts +2 -3
- package/lib/interceptors/ClientRequest/http.request.js +3 -6
- package/lib/interceptors/ClientRequest/http.request.js.map +1 -1
- package/lib/interceptors/ClientRequest/index.d.ts +14 -4
- package/lib/interceptors/ClientRequest/index.js +59 -46
- package/lib/interceptors/ClientRequest/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js +7 -2
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +11 -4
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +110 -58
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/index.d.ts +11 -5
- package/lib/interceptors/XMLHttpRequest/index.js +43 -25
- package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
- package/lib/interceptors/fetch/index.d.ts +8 -2
- package/lib/interceptors/fetch/index.js +120 -68
- package/lib/interceptors/fetch/index.js.map +1 -1
- package/lib/presets/browser.d.ts +3 -1
- package/lib/presets/browser.js +2 -2
- package/lib/presets/browser.js.map +1 -1
- package/lib/presets/node.d.ts +3 -1
- package/lib/presets/node.js +1 -1
- package/lib/presets/node.js.map +1 -1
- package/lib/utils/AsyncEventEmitter.d.ts +29 -0
- package/lib/utils/AsyncEventEmitter.js +241 -0
- package/lib/utils/AsyncEventEmitter.js.map +1 -0
- package/lib/utils/createLazyCallback.d.ts +11 -0
- package/lib/utils/createLazyCallback.js +75 -0
- package/lib/utils/createLazyCallback.js.map +1 -0
- package/lib/utils/nextTick.d.ts +2 -0
- package/lib/utils/nextTick.js +16 -0
- package/lib/utils/nextTick.js.map +1 -0
- package/lib/utils/toIsoResponse.d.ts +1 -1
- package/package.json +7 -7
- package/src/BatchInterceptor.test.ts +113 -0
- package/src/BatchInterceptor.ts +60 -0
- package/src/Interceptor.test.ts +166 -0
- package/src/Interceptor.ts +226 -0
- package/src/RemoteInterceptor.ts +176 -0
- package/src/glossary.ts +42 -0
- package/src/index.ts +2 -2
- package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +87 -70
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +150 -99
- package/src/interceptors/ClientRequest/http.get.ts +7 -11
- package/src/interceptors/ClientRequest/http.request.ts +8 -12
- package/src/interceptors/ClientRequest/index.test.ts +43 -0
- package/src/interceptors/ClientRequest/index.ts +46 -46
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +9 -0
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +9 -2
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +215 -159
- package/src/interceptors/XMLHttpRequest/index.ts +41 -23
- package/src/interceptors/fetch/index.ts +81 -55
- package/src/presets/browser.ts +3 -3
- package/src/presets/node.ts +3 -3
- package/src/utils/AsyncEventEmitter.test.ts +68 -0
- package/src/utils/AsyncEventEmitter.ts +171 -0
- package/src/utils/createLazyCallback.ts +49 -0
- package/src/utils/nextTick.ts +11 -0
- package/src/utils/toIsoResponse.ts +1 -1
- package/lib/createInterceptor.d.ts +0 -54
- package/lib/createInterceptor.js +0 -27
- package/lib/createInterceptor.js.map +0 -1
- package/lib/remote.d.ts +0 -21
- package/lib/remote.js +0 -178
- package/lib/remote.js.map +0 -1
- package/src/createInterceptor.ts +0 -100
- package/src/remote.ts +0 -174
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* XMLHttpRequest override class.
|
|
3
3
|
* Inspired by https://github.com/marvinhagemeister/xhr-mocklet.
|
|
4
4
|
*/
|
|
5
|
+
import type { Debugger } from 'debug'
|
|
5
6
|
import { until } from '@open-draft/until'
|
|
6
7
|
import {
|
|
7
8
|
Headers,
|
|
@@ -10,14 +11,14 @@ import {
|
|
|
10
11
|
headersToString,
|
|
11
12
|
} from 'headers-polyfill'
|
|
12
13
|
import { DOMParser } from '@xmldom/xmldom'
|
|
13
|
-
import {
|
|
14
|
+
import { InteractiveIsomorphicRequest, IsomorphicRequest } from '../../glossary'
|
|
14
15
|
import { parseJson } from '../../utils/parseJson'
|
|
15
16
|
import { toIsoResponse } from '../../utils/toIsoResponse'
|
|
16
17
|
import { uuidv4 } from '../../utils/uuid'
|
|
17
18
|
import { bufferFrom } from './utils/bufferFrom'
|
|
18
19
|
import { createEvent } from './utils/createEvent'
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
import type { XMLHttpRequestEmitter } from '.'
|
|
21
|
+
import { createLazyCallback } from '../../utils/createLazyCallback'
|
|
21
22
|
|
|
22
23
|
type XMLHttpRequestEventHandler = (
|
|
23
24
|
this: XMLHttpRequest,
|
|
@@ -30,9 +31,9 @@ interface XMLHttpRequestEvent<EventMap extends any> {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
interface CreateXMLHttpRequestOverrideOptions {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
XMLHttpRequest: typeof window.XMLHttpRequest
|
|
35
|
+
emitter: XMLHttpRequestEmitter
|
|
36
|
+
log: Debugger
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
interface InternalXMLHttpRequestEventTargetEventMap
|
|
@@ -40,11 +41,16 @@ interface InternalXMLHttpRequestEventTargetEventMap
|
|
|
40
41
|
readystatechange: Event
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
export type ExtractCallbacks<Key extends string> = Key extends
|
|
45
|
+
| 'abort'
|
|
46
|
+
| `on${infer _CallbackName}`
|
|
47
|
+
? Key
|
|
48
|
+
: never
|
|
49
|
+
|
|
43
50
|
export const createXMLHttpRequestOverride = (
|
|
44
51
|
options: CreateXMLHttpRequestOverrideOptions
|
|
45
52
|
) => {
|
|
46
|
-
const {
|
|
47
|
-
let debug = createDebug('XHR')
|
|
53
|
+
const { XMLHttpRequest, emitter, log } = options
|
|
48
54
|
|
|
49
55
|
return class XMLHttpRequestOverride implements XMLHttpRequest {
|
|
50
56
|
_requestHeaders: Headers
|
|
@@ -54,6 +60,8 @@ export const createXMLHttpRequestOverride = (
|
|
|
54
60
|
_events: XMLHttpRequestEvent<InternalXMLHttpRequestEventTargetEventMap>[] =
|
|
55
61
|
[]
|
|
56
62
|
|
|
63
|
+
log: Debugger = log
|
|
64
|
+
|
|
57
65
|
/* Request state */
|
|
58
66
|
public static readonly UNSENT = 0
|
|
59
67
|
public static readonly OPENED = 1
|
|
@@ -142,11 +150,11 @@ export const createXMLHttpRequestOverride = (
|
|
|
142
150
|
return
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
this.log('readyState change %d -> %d', this.readyState, nextState)
|
|
146
154
|
this.readyState = nextState
|
|
147
155
|
|
|
148
156
|
if (nextState !== this.UNSENT) {
|
|
149
|
-
|
|
157
|
+
this.log('triggerring readystate change...')
|
|
150
158
|
this.trigger('readystatechange')
|
|
151
159
|
}
|
|
152
160
|
}
|
|
@@ -160,8 +168,8 @@ export const createXMLHttpRequestOverride = (
|
|
|
160
168
|
readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
|
|
161
169
|
})
|
|
162
170
|
>(eventName: K, options?: ProgressEventInit) {
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
this.log('trigger "%s" (%d)', eventName, this.readyState)
|
|
172
|
+
this.log('resolve listener for event "%s"', eventName)
|
|
165
173
|
|
|
166
174
|
// @ts-expect-error XMLHttpRequest class has no index signature.
|
|
167
175
|
const callback = this[`on${eventName}`] as XMLHttpRequestEventHandler
|
|
@@ -169,7 +177,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
169
177
|
|
|
170
178
|
for (const event of this._events) {
|
|
171
179
|
if (event.name === eventName) {
|
|
172
|
-
|
|
180
|
+
log(
|
|
173
181
|
'calling mock event listener "%s" (%d)',
|
|
174
182
|
eventName,
|
|
175
183
|
this.readyState
|
|
@@ -182,7 +190,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
reset() {
|
|
185
|
-
|
|
193
|
+
this.log('reset')
|
|
186
194
|
|
|
187
195
|
this.setReadyState(this.UNSENT)
|
|
188
196
|
this.status = 200
|
|
@@ -203,8 +211,8 @@ export const createXMLHttpRequestOverride = (
|
|
|
203
211
|
user?: string,
|
|
204
212
|
password?: string
|
|
205
213
|
) {
|
|
206
|
-
|
|
207
|
-
|
|
214
|
+
this.log = this.log.extend(`request ${method} ${url}`)
|
|
215
|
+
this.log('open', { method, url, async, user, password })
|
|
208
216
|
|
|
209
217
|
this.reset()
|
|
210
218
|
this.setReadyState(this.OPENED)
|
|
@@ -222,7 +230,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
public send(data?: string) {
|
|
225
|
-
|
|
233
|
+
this.log('send %s %s', this.method, this.url)
|
|
226
234
|
|
|
227
235
|
this.data = data || ''
|
|
228
236
|
|
|
@@ -237,10 +245,10 @@ export const createXMLHttpRequestOverride = (
|
|
|
237
245
|
url = new URL(this.url, window.location.href)
|
|
238
246
|
}
|
|
239
247
|
|
|
240
|
-
|
|
248
|
+
this.log('request headers', this._requestHeaders)
|
|
241
249
|
|
|
242
250
|
// Create an intercepted request instance exposed to the request intercepting middleware.
|
|
243
|
-
const
|
|
251
|
+
const isomorphicRequest: IsomorphicRequest = {
|
|
244
252
|
id: uuidv4(),
|
|
245
253
|
url,
|
|
246
254
|
method: this.method,
|
|
@@ -249,160 +257,182 @@ export const createXMLHttpRequestOverride = (
|
|
|
249
257
|
body: this.data,
|
|
250
258
|
}
|
|
251
259
|
|
|
252
|
-
|
|
260
|
+
const interactiveIsomorphicRequest: InteractiveIsomorphicRequest = {
|
|
261
|
+
...isomorphicRequest,
|
|
262
|
+
respondWith: createLazyCallback(),
|
|
263
|
+
}
|
|
253
264
|
|
|
254
|
-
|
|
265
|
+
this.log(
|
|
266
|
+
'emitting the "request" event for %d listener(s)...',
|
|
267
|
+
emitter.listenerCount('request')
|
|
268
|
+
)
|
|
269
|
+
emitter.emit('request', interactiveIsomorphicRequest)
|
|
270
|
+
|
|
271
|
+
this.log('awaiting mocked response...')
|
|
272
|
+
|
|
273
|
+
Promise.resolve(
|
|
274
|
+
until(async () => {
|
|
275
|
+
await emitter.untilIdle('request')
|
|
276
|
+
this.log('all request listeners have been resolved!')
|
|
277
|
+
|
|
278
|
+
const [mockedResponse] =
|
|
279
|
+
await interactiveIsomorphicRequest.respondWith.invoked()
|
|
280
|
+
this.log('event.respondWith called with:', mockedResponse)
|
|
281
|
+
|
|
282
|
+
return mockedResponse
|
|
283
|
+
})
|
|
284
|
+
).then(([middlewareException, mockedResponse]) => {
|
|
285
|
+
// When the request middleware throws an exception, error the request.
|
|
286
|
+
// This cancels the request and is similar to a network error.
|
|
287
|
+
if (middlewareException) {
|
|
288
|
+
this.log(
|
|
289
|
+
'middleware function threw an exception!',
|
|
290
|
+
middlewareException
|
|
291
|
+
)
|
|
255
292
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// This cancels the request and is similar to a network error.
|
|
260
|
-
if (middlewareException) {
|
|
261
|
-
debug(
|
|
262
|
-
'middleware function threw an exception!',
|
|
263
|
-
middlewareException
|
|
264
|
-
)
|
|
293
|
+
// No way to propagate the actual error message.
|
|
294
|
+
this.trigger('error')
|
|
295
|
+
this.abort()
|
|
265
296
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
this.abort()
|
|
297
|
+
return
|
|
298
|
+
}
|
|
269
299
|
|
|
270
|
-
|
|
271
|
-
|
|
300
|
+
// Return a mocked response, if provided in the middleware.
|
|
301
|
+
if (mockedResponse) {
|
|
302
|
+
this.log('received mocked response', mockedResponse)
|
|
303
|
+
|
|
304
|
+
// Trigger a loadstart event to indicate the initialization of the fetch.
|
|
305
|
+
this.trigger('loadstart')
|
|
306
|
+
|
|
307
|
+
this.status = mockedResponse.status || 200
|
|
308
|
+
this.statusText = mockedResponse.statusText || 'OK'
|
|
309
|
+
this._responseHeaders = mockedResponse.headers
|
|
310
|
+
? objectToHeaders(mockedResponse.headers)
|
|
311
|
+
: new Headers()
|
|
272
312
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
debug('received mocked response', mockedResponse)
|
|
313
|
+
this.log('set response status', this.status, this.statusText)
|
|
314
|
+
this.log('set response headers', this._responseHeaders)
|
|
276
315
|
|
|
277
|
-
|
|
278
|
-
|
|
316
|
+
// Mark that response headers has been received
|
|
317
|
+
// and trigger a ready state event to reflect received headers
|
|
318
|
+
// in a custom `onreadystatechange` callback.
|
|
319
|
+
this.setReadyState(this.HEADERS_RECEIVED)
|
|
279
320
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
: new Headers()
|
|
321
|
+
this.log('response type', this.responseType)
|
|
322
|
+
this.response = this.getResponseBody(mockedResponse.body)
|
|
323
|
+
this.responseText = mockedResponse.body || ''
|
|
324
|
+
this.responseXML = this.getResponseXML()
|
|
285
325
|
|
|
286
|
-
|
|
287
|
-
debug('set response headers', this._responseHeaders)
|
|
326
|
+
this.log('set response body', this.response)
|
|
288
327
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// in a custom `onreadystatechange` callback.
|
|
292
|
-
this.setReadyState(this.HEADERS_RECEIVED)
|
|
328
|
+
if (mockedResponse.body && this.response) {
|
|
329
|
+
this.setReadyState(this.LOADING)
|
|
293
330
|
|
|
294
|
-
|
|
295
|
-
this.response
|
|
296
|
-
|
|
297
|
-
|
|
331
|
+
// Presense of the mocked response implies a response body (not null).
|
|
332
|
+
// Presense of the coerced `this.response` implies the mocked body is valid.
|
|
333
|
+
const bodyBuffer = bufferFrom(mockedResponse.body)
|
|
334
|
+
|
|
335
|
+
// Trigger a progress event based on the mocked response body.
|
|
336
|
+
this.trigger('progress', {
|
|
337
|
+
loaded: bodyBuffer.length,
|
|
338
|
+
total: bodyBuffer.length,
|
|
339
|
+
})
|
|
340
|
+
}
|
|
298
341
|
|
|
299
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Explicitly mark the request as done so its response never hangs.
|
|
344
|
+
* @see https://github.com/mswjs/interceptors/issues/13
|
|
345
|
+
*/
|
|
346
|
+
this.setReadyState(this.DONE)
|
|
347
|
+
|
|
348
|
+
// Trigger a load event to indicate the fetch has succeeded.
|
|
349
|
+
this.trigger('load')
|
|
350
|
+
// Trigger a loadend event to indicate the fetch has completed.
|
|
351
|
+
this.trigger('loadend')
|
|
352
|
+
|
|
353
|
+
emitter.emit(
|
|
354
|
+
'response',
|
|
355
|
+
isomorphicRequest,
|
|
356
|
+
toIsoResponse(mockedResponse)
|
|
357
|
+
)
|
|
358
|
+
} else {
|
|
359
|
+
this.log('no mocked response received!')
|
|
360
|
+
|
|
361
|
+
// Perform an original request, when the request middleware returned no mocked response.
|
|
362
|
+
const originalRequest = new XMLHttpRequest()
|
|
363
|
+
|
|
364
|
+
this.log('opening an original request %s %s', this.method, this.url)
|
|
365
|
+
originalRequest.open(
|
|
366
|
+
this.method,
|
|
367
|
+
this.url,
|
|
368
|
+
this.async ?? true,
|
|
369
|
+
this.user,
|
|
370
|
+
this.password
|
|
371
|
+
)
|
|
300
372
|
|
|
301
|
-
|
|
302
|
-
|
|
373
|
+
// Reflect a successful state of the original request
|
|
374
|
+
// on the patched instance.
|
|
375
|
+
originalRequest.addEventListener('load', () => {
|
|
376
|
+
this.log('original "onload"')
|
|
303
377
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
378
|
+
this.status = originalRequest.status
|
|
379
|
+
this.statusText = originalRequest.statusText
|
|
380
|
+
this.responseURL = originalRequest.responseURL
|
|
381
|
+
this.responseType = originalRequest.responseType
|
|
382
|
+
this.response = originalRequest.response
|
|
383
|
+
this.responseText = originalRequest.responseText
|
|
384
|
+
this.responseXML = originalRequest.responseXML
|
|
307
385
|
|
|
308
|
-
|
|
309
|
-
this.trigger('progress', {
|
|
310
|
-
loaded: bodyBuffer.length,
|
|
311
|
-
total: bodyBuffer.length,
|
|
312
|
-
})
|
|
313
|
-
}
|
|
386
|
+
this.log('set mock request readyState to DONE')
|
|
314
387
|
|
|
388
|
+
// Explicitly mark the mocked request instance as done
|
|
389
|
+
// so the response never hangs.
|
|
315
390
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
391
|
+
* @note `readystatechange` listener is called TWICE
|
|
392
|
+
* in the case of unhandled request.
|
|
318
393
|
*/
|
|
319
394
|
this.setReadyState(this.DONE)
|
|
320
395
|
|
|
321
|
-
|
|
322
|
-
this.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const originalRequest = new pureXMLHttpRequest()
|
|
332
|
-
|
|
333
|
-
debug('opening an original request %s %s', this.method, this.url)
|
|
334
|
-
originalRequest.open(
|
|
335
|
-
this.method,
|
|
336
|
-
this.url,
|
|
337
|
-
this.async ?? true,
|
|
338
|
-
this.user,
|
|
339
|
-
this.password
|
|
396
|
+
this.log('received original response', this.status, this.statusText)
|
|
397
|
+
this.log('original response body:', this.response)
|
|
398
|
+
|
|
399
|
+
const responseHeaders = originalRequest.getAllResponseHeaders()
|
|
400
|
+
this.log('original response headers:\n', responseHeaders)
|
|
401
|
+
|
|
402
|
+
this._responseHeaders = stringToHeaders(responseHeaders)
|
|
403
|
+
this.log(
|
|
404
|
+
'original response headers (normalized)',
|
|
405
|
+
this._responseHeaders
|
|
340
406
|
)
|
|
341
407
|
|
|
342
|
-
|
|
343
|
-
// on the patched instance.
|
|
344
|
-
originalRequest.addEventListener('load', () => {
|
|
345
|
-
debug('original "onload"')
|
|
346
|
-
|
|
347
|
-
this.status = originalRequest.status
|
|
348
|
-
this.statusText = originalRequest.statusText
|
|
349
|
-
this.responseURL = originalRequest.responseURL
|
|
350
|
-
this.responseType = originalRequest.responseType
|
|
351
|
-
this.response = originalRequest.response
|
|
352
|
-
this.responseText = originalRequest.responseText
|
|
353
|
-
this.responseXML = originalRequest.responseXML
|
|
354
|
-
|
|
355
|
-
debug('set mock request readyState to DONE')
|
|
356
|
-
|
|
357
|
-
// Explicitly mark the mocked request instance as done
|
|
358
|
-
// so the response never hangs.
|
|
359
|
-
/**
|
|
360
|
-
* @note `readystatechange` listener is called TWICE
|
|
361
|
-
* in the case of unhandled request.
|
|
362
|
-
*/
|
|
363
|
-
this.setReadyState(this.DONE)
|
|
364
|
-
|
|
365
|
-
debug('received original response', this.status, this.statusText)
|
|
366
|
-
debug('original response body:', this.response)
|
|
367
|
-
|
|
368
|
-
const responseHeaders = originalRequest.getAllResponseHeaders()
|
|
369
|
-
debug('original response headers:\n', responseHeaders)
|
|
370
|
-
|
|
371
|
-
this._responseHeaders = stringToHeaders(responseHeaders)
|
|
372
|
-
debug(
|
|
373
|
-
'original response headers (normalized)',
|
|
374
|
-
this._responseHeaders
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
debug('original response finished')
|
|
378
|
-
|
|
379
|
-
observer.emit('response', isoRequest, {
|
|
380
|
-
status: originalRequest.status,
|
|
381
|
-
statusText: originalRequest.statusText,
|
|
382
|
-
headers: this._responseHeaders,
|
|
383
|
-
body: originalRequest.response,
|
|
384
|
-
})
|
|
385
|
-
})
|
|
408
|
+
this.log('original response finished')
|
|
386
409
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
410
|
+
emitter.emit('response', isomorphicRequest, {
|
|
411
|
+
status: originalRequest.status,
|
|
412
|
+
statusText: originalRequest.statusText,
|
|
413
|
+
headers: this._responseHeaders,
|
|
414
|
+
body: originalRequest.response,
|
|
415
|
+
})
|
|
416
|
+
})
|
|
392
417
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
418
|
+
// Assign callbacks and event listeners from the intercepted XHR instance
|
|
419
|
+
// to the original XHR instance.
|
|
420
|
+
this.propagateCallbacks(originalRequest)
|
|
421
|
+
this.propagateListeners(originalRequest)
|
|
422
|
+
this.propagateHeaders(originalRequest, this._requestHeaders)
|
|
396
423
|
|
|
397
|
-
|
|
398
|
-
originalRequest.
|
|
424
|
+
if (this.async) {
|
|
425
|
+
originalRequest.timeout = this.timeout
|
|
399
426
|
}
|
|
427
|
+
|
|
428
|
+
this.log('send', this.data)
|
|
429
|
+
originalRequest.send(this.data)
|
|
400
430
|
}
|
|
401
|
-
)
|
|
431
|
+
})
|
|
402
432
|
}
|
|
403
433
|
|
|
404
434
|
public abort() {
|
|
405
|
-
|
|
435
|
+
this.log('abort')
|
|
406
436
|
|
|
407
437
|
if (this.readyState > this.UNSENT && this.readyState < this.DONE) {
|
|
408
438
|
this.setReadyState(this.UNSENT)
|
|
@@ -415,15 +445,15 @@ export const createXMLHttpRequestOverride = (
|
|
|
415
445
|
}
|
|
416
446
|
|
|
417
447
|
public setRequestHeader(name: string, value: string) {
|
|
418
|
-
|
|
448
|
+
this.log('set request header "%s" to "%s"', name, value)
|
|
419
449
|
this._requestHeaders.append(name, value)
|
|
420
450
|
}
|
|
421
451
|
|
|
422
452
|
public getResponseHeader(name: string): string | null {
|
|
423
|
-
|
|
453
|
+
this.log('get response header "%s"', name)
|
|
424
454
|
|
|
425
455
|
if (this.readyState < this.HEADERS_RECEIVED) {
|
|
426
|
-
|
|
456
|
+
this.log(
|
|
427
457
|
'cannot return a header: headers not received (state: %s)',
|
|
428
458
|
this.readyState
|
|
429
459
|
)
|
|
@@ -432,7 +462,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
432
462
|
|
|
433
463
|
const headerValue = this._responseHeaders.get(name)
|
|
434
464
|
|
|
435
|
-
|
|
465
|
+
this.log(
|
|
436
466
|
'resolved response header "%s" to "%s"',
|
|
437
467
|
name,
|
|
438
468
|
headerValue,
|
|
@@ -443,10 +473,10 @@ export const createXMLHttpRequestOverride = (
|
|
|
443
473
|
}
|
|
444
474
|
|
|
445
475
|
public getAllResponseHeaders(): string {
|
|
446
|
-
|
|
476
|
+
this.log('get all response headers')
|
|
447
477
|
|
|
448
478
|
if (this.readyState < this.HEADERS_RECEIVED) {
|
|
449
|
-
|
|
479
|
+
this.log(
|
|
450
480
|
'cannot return headers: headers not received (state: %s)',
|
|
451
481
|
this.readyState
|
|
452
482
|
)
|
|
@@ -459,7 +489,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
459
489
|
public addEventListener<
|
|
460
490
|
K extends keyof InternalXMLHttpRequestEventTargetEventMap
|
|
461
491
|
>(name: K, listener: XMLHttpRequestEventHandler) {
|
|
462
|
-
|
|
492
|
+
this.log('addEventListener', name, listener)
|
|
463
493
|
this._events.push({
|
|
464
494
|
name,
|
|
465
495
|
listener,
|
|
@@ -470,7 +500,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
470
500
|
name: K,
|
|
471
501
|
listener: (event?: XMLHttpRequestEventMap[K]) => void
|
|
472
502
|
): void {
|
|
473
|
-
|
|
503
|
+
this.log('removeEventListener', name, listener)
|
|
474
504
|
this._events = this._events.filter((storedEvent) => {
|
|
475
505
|
return storedEvent.name !== name && storedEvent.listener !== listener
|
|
476
506
|
})
|
|
@@ -484,18 +514,18 @@ export const createXMLHttpRequestOverride = (
|
|
|
484
514
|
getResponseBody(body: string | undefined) {
|
|
485
515
|
// Handle an improperly set "null" value of the mocked response body.
|
|
486
516
|
const textBody = body ?? ''
|
|
487
|
-
|
|
517
|
+
this.log('coerced response body to', textBody)
|
|
488
518
|
|
|
489
519
|
switch (this.responseType) {
|
|
490
520
|
case 'json': {
|
|
491
|
-
|
|
521
|
+
this.log('resolving response body as JSON')
|
|
492
522
|
return parseJson(textBody)
|
|
493
523
|
}
|
|
494
524
|
|
|
495
525
|
case 'blob': {
|
|
496
526
|
const blobType =
|
|
497
527
|
this.getResponseHeader('content-type') || 'text/plain'
|
|
498
|
-
|
|
528
|
+
this.log('resolving response body as Blob', { type: blobType })
|
|
499
529
|
|
|
500
530
|
return new Blob([textBody], {
|
|
501
531
|
type: blobType,
|
|
@@ -503,7 +533,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
503
533
|
}
|
|
504
534
|
|
|
505
535
|
case 'arraybuffer': {
|
|
506
|
-
|
|
536
|
+
this.log('resolving response body as ArrayBuffer')
|
|
507
537
|
const arrayBuffer = bufferFrom(textBody)
|
|
508
538
|
return arrayBuffer
|
|
509
539
|
}
|
|
@@ -526,6 +556,28 @@ export const createXMLHttpRequestOverride = (
|
|
|
526
556
|
* to the given XMLHttpRequest instance.
|
|
527
557
|
*/
|
|
528
558
|
propagateCallbacks(request: XMLHttpRequest) {
|
|
559
|
+
this.log('propagating request callbacks to the original request')
|
|
560
|
+
const callbackNames: Array<ExtractCallbacks<keyof XMLHttpRequest>> = [
|
|
561
|
+
'abort',
|
|
562
|
+
'onerror',
|
|
563
|
+
'ontimeout',
|
|
564
|
+
'onload',
|
|
565
|
+
'onloadstart',
|
|
566
|
+
'onloadend',
|
|
567
|
+
'onprogress',
|
|
568
|
+
'onreadystatechange',
|
|
569
|
+
]
|
|
570
|
+
|
|
571
|
+
for (const callbackName of callbackNames) {
|
|
572
|
+
const callback = this[callbackName]
|
|
573
|
+
|
|
574
|
+
if (callback) {
|
|
575
|
+
request[callbackName] = this[callbackName] as any
|
|
576
|
+
|
|
577
|
+
this.log('propagated the "%s" callback', callbackName, callback)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
529
581
|
request.onabort = this.abort
|
|
530
582
|
request.onerror = this.onerror
|
|
531
583
|
request.ontimeout = this.ontimeout
|
|
@@ -541,7 +593,7 @@ export const createXMLHttpRequestOverride = (
|
|
|
541
593
|
* to the given XMLHttpRequest instance.
|
|
542
594
|
*/
|
|
543
595
|
propagateListeners(request: XMLHttpRequest) {
|
|
544
|
-
|
|
596
|
+
this.log(
|
|
545
597
|
'propagating request listeners (%d) to the original request',
|
|
546
598
|
this._events.length,
|
|
547
599
|
this._events
|
|
@@ -553,11 +605,15 @@ export const createXMLHttpRequestOverride = (
|
|
|
553
605
|
}
|
|
554
606
|
|
|
555
607
|
propagateHeaders(request: XMLHttpRequest, headers: Headers) {
|
|
556
|
-
|
|
608
|
+
this.log('propagating request headers to the original request', headers)
|
|
557
609
|
|
|
558
610
|
// Preserve the request headers casing.
|
|
559
611
|
Object.entries(headers.raw()).forEach(([name, value]) => {
|
|
560
|
-
|
|
612
|
+
this.log(
|
|
613
|
+
'setting "%s" (%s) header on the original request',
|
|
614
|
+
name,
|
|
615
|
+
value
|
|
616
|
+
)
|
|
561
617
|
request.setRequestHeader(name, value)
|
|
562
618
|
})
|
|
563
619
|
}
|
|
@@ -1,34 +1,52 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
|
+
HttpRequestEventMap,
|
|
3
|
+
InteractiveIsomorphicRequest,
|
|
4
|
+
} from '../../glossary'
|
|
5
|
+
import { Interceptor } from '../../Interceptor'
|
|
6
|
+
import { AsyncEventEmitter } from '../../utils/AsyncEventEmitter'
|
|
2
7
|
import { createXMLHttpRequestOverride } from './XMLHttpRequestOverride'
|
|
3
8
|
|
|
4
|
-
|
|
9
|
+
export type XMLHttpRequestEventListener = (
|
|
10
|
+
request: InteractiveIsomorphicRequest
|
|
11
|
+
) => Promise<void> | void
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
// Although executed in node, certain processes emulate the DOM-like environment
|
|
8
|
-
// (i.e. `js-dom` in Jest). The `window` object would be avilable in such environments.
|
|
9
|
-
typeof window === 'undefined' ? undefined : window.XMLHttpRequest
|
|
13
|
+
export type XMLHttpRequestEmitter = AsyncEventEmitter<HttpRequestEventMap>
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
export const interceptXMLHttpRequest: Interceptor = (observer, resolver) => {
|
|
15
|
-
if (pureXMLHttpRequest) {
|
|
16
|
-
debug('patching "XMLHttpRequest" module...')
|
|
15
|
+
export class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
16
|
+
static symbol = Symbol('xhr')
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
resolver,
|
|
22
|
-
})
|
|
18
|
+
constructor() {
|
|
19
|
+
super(XMLHttpRequestInterceptor.symbol)
|
|
20
|
+
}
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
protected checkEnvironment() {
|
|
23
|
+
return (
|
|
24
|
+
typeof window !== 'undefined' &&
|
|
25
|
+
typeof window.XMLHttpRequest !== 'undefined'
|
|
26
|
+
)
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
protected setup() {
|
|
30
|
+
const log = this.log.extend('setup')
|
|
31
|
+
|
|
32
|
+
log('patching "XMLHttpRequest" module...')
|
|
33
|
+
|
|
34
|
+
const PureXMLHttpRequest = window.XMLHttpRequest
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
window.XMLHttpRequest = createXMLHttpRequestOverride({
|
|
37
|
+
XMLHttpRequest: PureXMLHttpRequest,
|
|
38
|
+
emitter: this.emitter,
|
|
39
|
+
log: this.log,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
log('native "XMLHttpRequest" module patched!', window.XMLHttpRequest.name)
|
|
43
|
+
|
|
44
|
+
this.subscriptions.push(() => {
|
|
45
|
+
window.XMLHttpRequest = PureXMLHttpRequest
|
|
46
|
+
log(
|
|
47
|
+
'native "XMLHttpRequest" module restored!',
|
|
48
|
+
window.XMLHttpRequest.name
|
|
49
|
+
)
|
|
50
|
+
})
|
|
33
51
|
}
|
|
34
52
|
}
|