@mswjs/interceptors 0.19.5 → 0.20.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/lib/interceptors/XMLHttpRequest/XMLHttpRequestController.d.ts +44 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestController.js +413 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestController.js.map +1 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestProxy.d.ts +20 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestProxy.js +75 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestProxy.js.map +1 -0
- package/lib/interceptors/XMLHttpRequest/index.js +9 -11
- package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
- package/lib/utils/createProxy.d.ts +8 -0
- package/lib/utils/createProxy.js +45 -0
- package/lib/utils/createProxy.js.map +1 -0
- package/package.json +1 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +578 -0
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +111 -0
- package/src/interceptors/XMLHttpRequest/index.ts +12 -13
- package/src/utils/createProxy.ts +78 -0
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +0 -93
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +0 -442
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +0 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +0 -684
|
@@ -1,684 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XMLHttpRequest override class.
|
|
3
|
-
* Inspired by https://github.com/marvinhagemeister/xhr-mocklet.
|
|
4
|
-
*/
|
|
5
|
-
import type { Debugger } from 'debug'
|
|
6
|
-
import { until } from '@open-draft/until'
|
|
7
|
-
import { Request } from '@remix-run/web-fetch'
|
|
8
|
-
import { Headers, stringToHeaders, headersToString } from 'headers-polyfill'
|
|
9
|
-
import { parseJson } from '../../utils/parseJson'
|
|
10
|
-
import { createEvent } from './utils/createEvent'
|
|
11
|
-
import type { XMLHttpRequestEmitter } from '.'
|
|
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 { isDomParserSupportedType } from './utils/isDomParserSupportedType'
|
|
22
|
-
|
|
23
|
-
type XMLHttpRequestEventHandler = (
|
|
24
|
-
this: XMLHttpRequest,
|
|
25
|
-
event: Event | ProgressEvent<any>
|
|
26
|
-
) => void
|
|
27
|
-
|
|
28
|
-
interface XMLHttpRequestEvent<EventMap extends any> {
|
|
29
|
-
name: keyof EventMap
|
|
30
|
-
listener: XMLHttpRequestEventHandler
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface CreateXMLHttpRequestOverrideOptions {
|
|
34
|
-
XMLHttpRequest: typeof window.XMLHttpRequest
|
|
35
|
-
emitter: XMLHttpRequestEmitter
|
|
36
|
-
log: Debugger
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface InternalXMLHttpRequestEventTargetEventMap
|
|
40
|
-
extends XMLHttpRequestEventTargetEventMap {
|
|
41
|
-
readystatechange: Event
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type ExtractCallbacks<Key extends string> = Key extends
|
|
45
|
-
| 'abort'
|
|
46
|
-
| `on${infer _CallbackName}`
|
|
47
|
-
? Key
|
|
48
|
-
: never
|
|
49
|
-
|
|
50
|
-
export const createXMLHttpRequestOverride = (
|
|
51
|
-
options: CreateXMLHttpRequestOverrideOptions
|
|
52
|
-
) => {
|
|
53
|
-
const { XMLHttpRequest, emitter, log } = options
|
|
54
|
-
|
|
55
|
-
return class XMLHttpRequestOverride implements XMLHttpRequest {
|
|
56
|
-
_requestHeaders: Headers
|
|
57
|
-
_responseHeaders: Headers
|
|
58
|
-
_responseBuffer: Uint8Array
|
|
59
|
-
|
|
60
|
-
// Collection of events modified by `addEventListener`/`removeEventListener` calls.
|
|
61
|
-
_events: XMLHttpRequestEvent<InternalXMLHttpRequestEventTargetEventMap>[] =
|
|
62
|
-
[]
|
|
63
|
-
|
|
64
|
-
log: Debugger = log
|
|
65
|
-
|
|
66
|
-
/* Request state */
|
|
67
|
-
public static readonly UNSENT = 0
|
|
68
|
-
public static readonly OPENED = 1
|
|
69
|
-
public static readonly HEADERS_RECEIVED = 2
|
|
70
|
-
public static readonly LOADING = 3
|
|
71
|
-
public static readonly DONE = 4
|
|
72
|
-
public readonly UNSENT = 0
|
|
73
|
-
public readonly OPENED = 1
|
|
74
|
-
public readonly HEADERS_RECEIVED = 2
|
|
75
|
-
public readonly LOADING = 3
|
|
76
|
-
public readonly DONE = 4
|
|
77
|
-
|
|
78
|
-
/* Custom public properties */
|
|
79
|
-
public method: string
|
|
80
|
-
public url: string
|
|
81
|
-
|
|
82
|
-
/* XHR public properties */
|
|
83
|
-
public withCredentials: boolean
|
|
84
|
-
public status: number
|
|
85
|
-
public statusText: string
|
|
86
|
-
public user?: string
|
|
87
|
-
public password?: string
|
|
88
|
-
public async?: boolean
|
|
89
|
-
public responseType: XMLHttpRequestResponseType
|
|
90
|
-
public responseURL: string
|
|
91
|
-
public upload: XMLHttpRequestUpload
|
|
92
|
-
public readyState: number
|
|
93
|
-
public onreadystatechange: (this: XMLHttpRequest, ev: Event) => any =
|
|
94
|
-
null as any
|
|
95
|
-
public timeout: number
|
|
96
|
-
|
|
97
|
-
/* Events */
|
|
98
|
-
public onabort: (
|
|
99
|
-
this: XMLHttpRequestEventTarget,
|
|
100
|
-
event: ProgressEvent
|
|
101
|
-
) => any = null as any
|
|
102
|
-
public onerror: (this: XMLHttpRequestEventTarget, event: Event) => any =
|
|
103
|
-
null as any
|
|
104
|
-
public onload: (
|
|
105
|
-
this: XMLHttpRequestEventTarget,
|
|
106
|
-
event: ProgressEvent
|
|
107
|
-
) => any = null as any
|
|
108
|
-
public onloadend: (
|
|
109
|
-
this: XMLHttpRequestEventTarget,
|
|
110
|
-
event: ProgressEvent
|
|
111
|
-
) => any = null as any
|
|
112
|
-
public onloadstart: (
|
|
113
|
-
this: XMLHttpRequestEventTarget,
|
|
114
|
-
event: ProgressEvent
|
|
115
|
-
) => any = null as any
|
|
116
|
-
public onprogress: (
|
|
117
|
-
this: XMLHttpRequestEventTarget,
|
|
118
|
-
event: ProgressEvent
|
|
119
|
-
) => any = null as any
|
|
120
|
-
public ontimeout: (
|
|
121
|
-
this: XMLHttpRequestEventTarget,
|
|
122
|
-
event: ProgressEvent
|
|
123
|
-
) => any = null as any
|
|
124
|
-
|
|
125
|
-
constructor() {
|
|
126
|
-
this.url = ''
|
|
127
|
-
this.method = 'GET'
|
|
128
|
-
this.readyState = this.UNSENT
|
|
129
|
-
this.withCredentials = false
|
|
130
|
-
this.status = 0
|
|
131
|
-
this.statusText = ''
|
|
132
|
-
this.responseType = 'text'
|
|
133
|
-
this.responseURL = ''
|
|
134
|
-
this.upload = {} as any
|
|
135
|
-
this.timeout = 0
|
|
136
|
-
|
|
137
|
-
this._requestHeaders = new Headers()
|
|
138
|
-
this._responseBuffer = new Uint8Array()
|
|
139
|
-
this._responseHeaders = new Headers()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
setReadyState(nextState: number): void {
|
|
143
|
-
if (nextState === this.readyState) {
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
this.log('readyState change %d -> %d', this.readyState, nextState)
|
|
148
|
-
this.readyState = nextState
|
|
149
|
-
|
|
150
|
-
if (nextState !== this.UNSENT) {
|
|
151
|
-
this.log('triggering readystate change...')
|
|
152
|
-
this.trigger('readystatechange')
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Triggers both direct callback and attached event listeners
|
|
158
|
-
* for the given event.
|
|
159
|
-
*/
|
|
160
|
-
trigger<
|
|
161
|
-
K extends keyof (XMLHttpRequestEventTargetEventMap & {
|
|
162
|
-
readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
|
|
163
|
-
})
|
|
164
|
-
>(eventName: K, options?: ProgressEventInit) {
|
|
165
|
-
this.log('trigger "%s" (%d)', eventName, this.readyState)
|
|
166
|
-
this.log('resolve listener for event "%s"', eventName)
|
|
167
|
-
|
|
168
|
-
const callback = this[`on${eventName}`] as XMLHttpRequestEventHandler
|
|
169
|
-
callback?.call(this, createEvent(this, eventName, options))
|
|
170
|
-
|
|
171
|
-
for (const event of this._events) {
|
|
172
|
-
if (event.name === eventName) {
|
|
173
|
-
log(
|
|
174
|
-
'calling mock event listener "%s" (%d)',
|
|
175
|
-
eventName,
|
|
176
|
-
this.readyState
|
|
177
|
-
)
|
|
178
|
-
event.listener.call(this, createEvent(this, eventName, options))
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return this
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
reset() {
|
|
186
|
-
this.log('reset')
|
|
187
|
-
|
|
188
|
-
this.setReadyState(this.UNSENT)
|
|
189
|
-
this.status = 0
|
|
190
|
-
this.statusText = ''
|
|
191
|
-
|
|
192
|
-
this._responseBuffer = new Uint8Array()
|
|
193
|
-
this._requestHeaders = new Headers()
|
|
194
|
-
this._responseHeaders = new Headers()
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
public async open(
|
|
198
|
-
method: string,
|
|
199
|
-
url: string,
|
|
200
|
-
async: boolean = true,
|
|
201
|
-
user?: string,
|
|
202
|
-
password?: string
|
|
203
|
-
) {
|
|
204
|
-
this.log = this.log.extend(`request ${method} ${url}`)
|
|
205
|
-
this.log('open', { method, url, async, user, password })
|
|
206
|
-
|
|
207
|
-
this.reset()
|
|
208
|
-
this.setReadyState(this.OPENED)
|
|
209
|
-
|
|
210
|
-
if (typeof url === 'undefined') {
|
|
211
|
-
this.url = method
|
|
212
|
-
this.method = 'GET'
|
|
213
|
-
} else {
|
|
214
|
-
this.url = url
|
|
215
|
-
this.method = method
|
|
216
|
-
this.async = async
|
|
217
|
-
this.user = user
|
|
218
|
-
this.password = password
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
public send(data?: string | ArrayBuffer) {
|
|
223
|
-
this.log('send %s %s', this.method, this.url)
|
|
224
|
-
|
|
225
|
-
const requestBuffer: ArrayBuffer | undefined =
|
|
226
|
-
typeof data === 'string' ? encodeBuffer(data) : data
|
|
227
|
-
|
|
228
|
-
let url: URL
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
url = new URL(this.url)
|
|
232
|
-
} catch (error) {
|
|
233
|
-
// Assume a relative URL, if construction of a new `URL` instance fails.
|
|
234
|
-
// Since `XMLHttpRequest` always executed in a DOM-like environment,
|
|
235
|
-
// resolve the relative request URL against the current window location.
|
|
236
|
-
url = new URL(this.url, window.location.href)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
this.log('request headers', this._requestHeaders)
|
|
240
|
-
|
|
241
|
-
// Create an intercepted request instance exposed to the request intercepting middleware.
|
|
242
|
-
const requestId = uuidv4()
|
|
243
|
-
const capturedRequest = new Request(url, {
|
|
244
|
-
method: this.method,
|
|
245
|
-
headers: this._requestHeaders,
|
|
246
|
-
credentials: this.withCredentials ? 'include' : 'omit',
|
|
247
|
-
body: requestBuffer,
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
const interactiveRequest = toInteractiveRequest(capturedRequest)
|
|
251
|
-
|
|
252
|
-
this.log(
|
|
253
|
-
'emitting the "request" event for %d listener(s)...',
|
|
254
|
-
emitter.listenerCount('request')
|
|
255
|
-
)
|
|
256
|
-
emitter.emit('request', interactiveRequest, requestId)
|
|
257
|
-
|
|
258
|
-
this.log('awaiting mocked response...')
|
|
259
|
-
|
|
260
|
-
Promise.resolve(
|
|
261
|
-
until(async () => {
|
|
262
|
-
await emitter.untilIdle(
|
|
263
|
-
'request',
|
|
264
|
-
({ args: [, pendingRequestId] }) => {
|
|
265
|
-
return pendingRequestId === requestId
|
|
266
|
-
}
|
|
267
|
-
)
|
|
268
|
-
this.log('all request listeners have been resolved!')
|
|
269
|
-
|
|
270
|
-
const [mockedResponse] =
|
|
271
|
-
await interactiveRequest.respondWith.invoked()
|
|
272
|
-
this.log('event.respondWith called with:', mockedResponse)
|
|
273
|
-
|
|
274
|
-
return mockedResponse
|
|
275
|
-
})
|
|
276
|
-
).then(([middlewareException, mockedResponse]) => {
|
|
277
|
-
// When the request middleware throws an exception, error the request.
|
|
278
|
-
// This cancels the request and is similar to a network error.
|
|
279
|
-
if (middlewareException) {
|
|
280
|
-
this.log(
|
|
281
|
-
'middleware function threw an exception!',
|
|
282
|
-
middlewareException
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
// Mark the request as complete.
|
|
286
|
-
this.setReadyState(this.DONE)
|
|
287
|
-
|
|
288
|
-
// No way to propagate the actual error message.
|
|
289
|
-
this.trigger('error')
|
|
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()
|
|
298
|
-
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Forward request headers modified in the "request" listener.
|
|
303
|
-
this._requestHeaders = new Headers(capturedRequest.headers)
|
|
304
|
-
|
|
305
|
-
// Return a mocked response, if provided in the middleware.
|
|
306
|
-
if (mockedResponse) {
|
|
307
|
-
const responseClone = mockedResponse.clone()
|
|
308
|
-
this.log('received mocked response', mockedResponse)
|
|
309
|
-
|
|
310
|
-
this.status = mockedResponse.status ?? 200
|
|
311
|
-
this.statusText = mockedResponse.statusText || 'OK'
|
|
312
|
-
this.log('set response status', this.status, this.statusText)
|
|
313
|
-
|
|
314
|
-
this._responseHeaders = new Headers(mockedResponse.headers || {})
|
|
315
|
-
this.log('set response headers', this._responseHeaders)
|
|
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
|
-
|
|
327
|
-
// Mark that response headers has been received
|
|
328
|
-
// and trigger a ready state event to reflect received headers
|
|
329
|
-
// in a custom "onreadystatechange" callback.
|
|
330
|
-
this.setReadyState(this.HEADERS_RECEIVED)
|
|
331
|
-
|
|
332
|
-
this.setReadyState(this.LOADING)
|
|
333
|
-
|
|
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)
|
|
340
|
-
|
|
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
|
-
})
|
|
347
|
-
|
|
348
|
-
// Trigger a loadend event to indicate the fetch has completed.
|
|
349
|
-
this.trigger('loadend', {
|
|
350
|
-
loaded: this._responseBuffer.byteLength,
|
|
351
|
-
total: totalLength,
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
emitter.emit('response', responseClone, capturedRequest, requestId)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (mockedResponse.body) {
|
|
358
|
-
const reader = mockedResponse.body.getReader()
|
|
359
|
-
|
|
360
|
-
const readNextChunk = async (): Promise<void> => {
|
|
361
|
-
const { value, done } = await reader.read()
|
|
362
|
-
|
|
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
|
-
}
|
|
387
|
-
} else {
|
|
388
|
-
this.log('no mocked response received!')
|
|
389
|
-
|
|
390
|
-
// Perform an original request, when the request middleware returned no mocked response.
|
|
391
|
-
const originalRequest = new XMLHttpRequest()
|
|
392
|
-
|
|
393
|
-
this.log('opening an original request %s %s', this.method, this.url)
|
|
394
|
-
originalRequest.open(
|
|
395
|
-
this.method,
|
|
396
|
-
this.url,
|
|
397
|
-
this.async ?? true,
|
|
398
|
-
this.user,
|
|
399
|
-
this.password
|
|
400
|
-
)
|
|
401
|
-
|
|
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
|
-
originalRequest.addEventListener('load', () => {
|
|
433
|
-
this.status = originalRequest.status
|
|
434
|
-
this.statusText = originalRequest.statusText
|
|
435
|
-
this.responseURL = originalRequest.responseURL
|
|
436
|
-
this.log('received original response', this.status, this.statusText)
|
|
437
|
-
|
|
438
|
-
// Explicitly mark the mocked request instance as done
|
|
439
|
-
// so the response never hangs.
|
|
440
|
-
this.setReadyState(this.DONE)
|
|
441
|
-
this.log('set mock request readyState to DONE')
|
|
442
|
-
|
|
443
|
-
this.log('original response body:', this.response)
|
|
444
|
-
this.log('original response finished!')
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
// Update the patched instance on the "loadend" event
|
|
448
|
-
// because it fires when the request settles (succeeds/errors).
|
|
449
|
-
originalRequest.addEventListener('loadend', () => {
|
|
450
|
-
this.log('original "loadend"')
|
|
451
|
-
|
|
452
|
-
emitter.emit(
|
|
453
|
-
'response',
|
|
454
|
-
createResponse(originalRequest, this._responseBuffer),
|
|
455
|
-
capturedRequest,
|
|
456
|
-
requestId
|
|
457
|
-
)
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
this.propagateHeaders(originalRequest, this._requestHeaders)
|
|
461
|
-
|
|
462
|
-
// Assign callbacks and event listeners from the intercepted XHR instance
|
|
463
|
-
// to the original XHR instance.
|
|
464
|
-
this.propagateCallbacks(originalRequest)
|
|
465
|
-
this.propagateListeners(originalRequest)
|
|
466
|
-
|
|
467
|
-
if (this.async) {
|
|
468
|
-
originalRequest.timeout = this.timeout
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* @note Set the intercepted request ID on the original request
|
|
473
|
-
* so that if it triggers any other interceptors, they don't attempt
|
|
474
|
-
* to process it once again. This happens when bypassing XMLHttpRequest
|
|
475
|
-
* because it's polyfilled with "http.ClientRequest" in JSDOM.
|
|
476
|
-
*/
|
|
477
|
-
originalRequest.setRequestHeader('X-Request-Id', requestId)
|
|
478
|
-
|
|
479
|
-
this.log('send', data)
|
|
480
|
-
originalRequest.send(data)
|
|
481
|
-
}
|
|
482
|
-
})
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
public get responseText(): string {
|
|
486
|
-
this.log('responseText()')
|
|
487
|
-
|
|
488
|
-
return decodeBuffer(this._responseBuffer)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
public get response(): unknown {
|
|
492
|
-
switch (this.responseType) {
|
|
493
|
-
case 'json': {
|
|
494
|
-
this.log('resolving response body as JSON')
|
|
495
|
-
return parseJson(this.responseText)
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
case 'arraybuffer': {
|
|
499
|
-
this.log('resolving response body as ArrayBuffer')
|
|
500
|
-
return toArrayBuffer(this._responseBuffer)
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
case 'blob': {
|
|
504
|
-
const mimeType =
|
|
505
|
-
this.getResponseHeader('content-type') || 'text/plain'
|
|
506
|
-
this.log('resolving response body as blog (%s)', mimeType)
|
|
507
|
-
return new Blob([this.responseText], { type: mimeType })
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
case 'document': {
|
|
511
|
-
this.log('resolving response body as XML')
|
|
512
|
-
return this.responseXML
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
default: {
|
|
516
|
-
return this.responseText
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
public get responseXML(): Document | null {
|
|
522
|
-
const contentType = this.getResponseHeader('content-type') || ''
|
|
523
|
-
this.log('responseXML() %s', contentType)
|
|
524
|
-
|
|
525
|
-
if (typeof DOMParser === 'undefined') {
|
|
526
|
-
console.warn(
|
|
527
|
-
'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'
|
|
528
|
-
)
|
|
529
|
-
return null
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (isDomParserSupportedType(contentType)) {
|
|
533
|
-
this.log('response content-type is XML, parsing...')
|
|
534
|
-
return new DOMParser().parseFromString(this.responseText, contentType)
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
this.log('response content type is not XML, returning null...')
|
|
538
|
-
return null
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
public abort() {
|
|
542
|
-
this.log('abort()')
|
|
543
|
-
|
|
544
|
-
if (this.readyState > this.UNSENT && this.readyState < this.DONE) {
|
|
545
|
-
this.reset()
|
|
546
|
-
this.trigger('abort')
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
public dispatchEvent() {
|
|
551
|
-
return false
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
public setRequestHeader(name: string, value: string) {
|
|
555
|
-
this.log('setRequestHeader() "%s" to "%s"', name, value)
|
|
556
|
-
this._requestHeaders.append(name, value)
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
public getResponseHeader(name: string): string | null {
|
|
560
|
-
this.log('getResponseHeader() "%s"', name)
|
|
561
|
-
|
|
562
|
-
if (this.readyState < this.HEADERS_RECEIVED) {
|
|
563
|
-
this.log(
|
|
564
|
-
'cannot return a header: headers not received (state: %s)',
|
|
565
|
-
this.readyState
|
|
566
|
-
)
|
|
567
|
-
return null
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const headerValue = this._responseHeaders.get(name)
|
|
571
|
-
|
|
572
|
-
this.log(
|
|
573
|
-
'resolved response header "%s" to "%s"',
|
|
574
|
-
name,
|
|
575
|
-
headerValue,
|
|
576
|
-
this._responseHeaders
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
return headerValue
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
public getAllResponseHeaders(): string {
|
|
583
|
-
this.log('getAllResponseHeaders()')
|
|
584
|
-
|
|
585
|
-
if (this.readyState < this.HEADERS_RECEIVED) {
|
|
586
|
-
this.log(
|
|
587
|
-
'cannot return headers: headers not received (state: %s)',
|
|
588
|
-
this.readyState
|
|
589
|
-
)
|
|
590
|
-
return ''
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return headersToString(this._responseHeaders)
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
public addEventListener<
|
|
597
|
-
Event extends keyof InternalXMLHttpRequestEventTargetEventMap
|
|
598
|
-
>(event: Event, listener: XMLHttpRequestEventHandler) {
|
|
599
|
-
this.log('addEventListener', event, listener)
|
|
600
|
-
this._events.push({
|
|
601
|
-
name: event,
|
|
602
|
-
listener,
|
|
603
|
-
})
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
public removeEventListener<Event extends keyof XMLHttpRequestEventMap>(
|
|
607
|
-
event: Event,
|
|
608
|
-
listener: (event?: XMLHttpRequestEventMap[Event]) => void
|
|
609
|
-
): void {
|
|
610
|
-
this.log('removeEventListener', name, listener)
|
|
611
|
-
this._events = this._events.filter((storedEvent) => {
|
|
612
|
-
return storedEvent.name !== event && storedEvent.listener !== listener
|
|
613
|
-
})
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
public overrideMimeType() {}
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* Propagates mock XMLHttpRequest instance callbacks
|
|
620
|
-
* to the given XMLHttpRequest instance.
|
|
621
|
-
*/
|
|
622
|
-
propagateCallbacks(request: XMLHttpRequest) {
|
|
623
|
-
this.log('propagating request callbacks to the original request')
|
|
624
|
-
const callbackNames: Array<ExtractCallbacks<keyof XMLHttpRequest>> = [
|
|
625
|
-
'abort',
|
|
626
|
-
'onerror',
|
|
627
|
-
'ontimeout',
|
|
628
|
-
'onload',
|
|
629
|
-
'onloadstart',
|
|
630
|
-
'onloadend',
|
|
631
|
-
'onprogress',
|
|
632
|
-
'onreadystatechange',
|
|
633
|
-
]
|
|
634
|
-
|
|
635
|
-
for (const callbackName of callbackNames) {
|
|
636
|
-
const callback = this[callbackName]
|
|
637
|
-
|
|
638
|
-
if (callback) {
|
|
639
|
-
request[callbackName] = this[callbackName] as any
|
|
640
|
-
|
|
641
|
-
this.log('propagated the "%s" callback', callbackName, callback)
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
request.onabort = this.abort
|
|
646
|
-
request.onerror = this.onerror
|
|
647
|
-
request.ontimeout = this.ontimeout
|
|
648
|
-
request.onload = this.onload
|
|
649
|
-
request.onloadstart = this.onloadstart
|
|
650
|
-
request.onloadend = this.onloadend
|
|
651
|
-
request.onprogress = this.onprogress
|
|
652
|
-
request.onreadystatechange = this.onreadystatechange
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Propagates the mock XMLHttpRequest instance listeners
|
|
657
|
-
* to the given XMLHttpRequest instance.
|
|
658
|
-
*/
|
|
659
|
-
propagateListeners(request: XMLHttpRequest) {
|
|
660
|
-
this.log(
|
|
661
|
-
'propagating request listeners (%d) to the original request',
|
|
662
|
-
this._events.length,
|
|
663
|
-
this._events
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
this._events.forEach(({ name, listener }) => {
|
|
667
|
-
request.addEventListener(name, listener)
|
|
668
|
-
})
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
propagateHeaders(request: XMLHttpRequest, headers: Headers) {
|
|
672
|
-
this.log('propagating request headers to the original request', headers)
|
|
673
|
-
|
|
674
|
-
for (const [headerName, headerValue] of headers) {
|
|
675
|
-
this.log(
|
|
676
|
-
'setting "%s" (%s) header on the original request',
|
|
677
|
-
headerName,
|
|
678
|
-
headerValue
|
|
679
|
-
)
|
|
680
|
-
request.setRequestHeader(headerName, headerValue)
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|