@mswjs/interceptors 0.13.1 → 0.13.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.
Files changed (48) hide show
  1. package/package.json +5 -4
  2. package/src/createInterceptor.ts +100 -0
  3. package/src/index.ts +5 -0
  4. package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +283 -0
  5. package/src/interceptors/ClientRequest/NodeClientRequest.ts +377 -0
  6. package/src/interceptors/ClientRequest/http.get.ts +32 -0
  7. package/src/interceptors/ClientRequest/http.request.ts +29 -0
  8. package/src/interceptors/ClientRequest/index.ts +61 -0
  9. package/src/interceptors/ClientRequest/utils/bodyBufferToString.test.ts +16 -0
  10. package/src/interceptors/ClientRequest/utils/bodyBufferToString.ts +7 -0
  11. package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.test.ts +20 -0
  12. package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.ts +41 -0
  13. package/src/interceptors/ClientRequest/utils/concatChunkToBuffer.test.ts +13 -0
  14. package/src/interceptors/ClientRequest/utils/concatChunkToBuffer.ts +10 -0
  15. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +44 -0
  16. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +38 -0
  17. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +336 -0
  18. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +205 -0
  19. package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.test.ts +40 -0
  20. package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.ts +51 -0
  21. package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.test.ts +35 -0
  22. package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.ts +36 -0
  23. package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +565 -0
  24. package/src/interceptors/XMLHttpRequest/index.ts +34 -0
  25. package/src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts +51 -0
  26. package/src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts +17 -0
  27. package/src/interceptors/XMLHttpRequest/utils/bufferFrom.test.ts +11 -0
  28. package/src/interceptors/XMLHttpRequest/utils/bufferFrom.ts +16 -0
  29. package/src/interceptors/XMLHttpRequest/utils/createEvent.test.ts +27 -0
  30. package/src/interceptors/XMLHttpRequest/utils/createEvent.ts +41 -0
  31. package/src/interceptors/fetch/index.ts +89 -0
  32. package/src/presets/browser.ts +8 -0
  33. package/src/presets/node.ts +8 -0
  34. package/src/remote.ts +176 -0
  35. package/src/utils/cloneObject.test.ts +93 -0
  36. package/src/utils/cloneObject.ts +34 -0
  37. package/src/utils/getCleanUrl.test.ts +31 -0
  38. package/src/utils/getCleanUrl.ts +6 -0
  39. package/src/utils/getRequestOptionsByUrl.ts +29 -0
  40. package/src/utils/getUrlByRequestOptions.test.ts +140 -0
  41. package/src/utils/getUrlByRequestOptions.ts +108 -0
  42. package/src/utils/isObject.test.ts +19 -0
  43. package/src/utils/isObject.ts +6 -0
  44. package/src/utils/parseJson.test.ts +9 -0
  45. package/src/utils/parseJson.ts +12 -0
  46. package/src/utils/toIsoResponse.test.ts +39 -0
  47. package/src/utils/toIsoResponse.ts +14 -0
  48. package/src/utils/uuid.ts +7 -0
@@ -0,0 +1,36 @@
1
+ import { debug } from 'debug'
2
+
3
+ const log = debug('http normalizeWriteArgs')
4
+
5
+ export type ClientRequestWriteCallback = (error?: Error | null) => void
6
+ export type ClientRequestWriteArgs = [
7
+ chunk: string | Buffer,
8
+ encoding?: BufferEncoding | ClientRequestWriteCallback,
9
+ callback?: ClientRequestWriteCallback
10
+ ]
11
+
12
+ export type NormalizedClientRequestWriteArgs = [
13
+ chunk: string | Buffer,
14
+ encoding?: BufferEncoding,
15
+ callback?: ClientRequestWriteCallback
16
+ ]
17
+
18
+ export function normalizeClientRequestWriteArgs(
19
+ args: ClientRequestWriteArgs
20
+ ): NormalizedClientRequestWriteArgs {
21
+ log('normalizing ClientRequest.write arguments...', args)
22
+
23
+ const chunk = args[0]
24
+ const encoding =
25
+ typeof args[1] === 'string' ? (args[1] as BufferEncoding) : undefined
26
+ const callback = typeof args[1] === 'function' ? args[1] : args[2]
27
+
28
+ const writeArgs: NormalizedClientRequestWriteArgs = [
29
+ chunk,
30
+ encoding,
31
+ callback,
32
+ ]
33
+ log('successfully normalized ClientRequest.write arguments:', writeArgs)
34
+
35
+ return writeArgs
36
+ }
@@ -0,0 +1,565 @@
1
+ /**
2
+ * XMLHttpRequest override class.
3
+ * Inspired by https://github.com/marvinhagemeister/xhr-mocklet.
4
+ */
5
+ import { until } from '@open-draft/until'
6
+ import {
7
+ Headers,
8
+ stringToHeaders,
9
+ objectToHeaders,
10
+ headersToString,
11
+ } from 'headers-utils'
12
+ import { DOMParser } from '@xmldom/xmldom'
13
+ import { IsomorphicRequest, Observer, Resolver } from '../../createInterceptor'
14
+ import { parseJson } from '../../utils/parseJson'
15
+ import { toIsoResponse } from '../../utils/toIsoResponse'
16
+ import { uuidv4 } from '../../utils/uuid'
17
+ import { bufferFrom } from './utils/bufferFrom'
18
+ import { createEvent } from './utils/createEvent'
19
+
20
+ const createDebug = require('debug')
21
+
22
+ type XMLHttpRequestEventHandler = (
23
+ this: XMLHttpRequest,
24
+ event: Event | ProgressEvent<any>
25
+ ) => void
26
+
27
+ interface XMLHttpRequestEvent<EventMap extends any> {
28
+ name: keyof EventMap
29
+ listener: XMLHttpRequestEventHandler
30
+ }
31
+
32
+ interface CreateXMLHttpRequestOverrideOptions {
33
+ pureXMLHttpRequest: typeof window.XMLHttpRequest
34
+ observer: Observer
35
+ resolver: Resolver
36
+ }
37
+
38
+ interface InternalXMLHttpRequestEventTargetEventMap
39
+ extends XMLHttpRequestEventTargetEventMap {
40
+ readystatechange: Event
41
+ }
42
+
43
+ export const createXMLHttpRequestOverride = (
44
+ options: CreateXMLHttpRequestOverrideOptions
45
+ ) => {
46
+ const { pureXMLHttpRequest, observer, resolver } = options
47
+ let debug = createDebug('XHR')
48
+
49
+ return class XMLHttpRequestOverride implements XMLHttpRequest {
50
+ _requestHeaders: Headers
51
+ _responseHeaders: Headers
52
+
53
+ // Collection of events modified by `addEventListener`/`removeEventListener` calls.
54
+ _events: XMLHttpRequestEvent<InternalXMLHttpRequestEventTargetEventMap>[] =
55
+ []
56
+
57
+ /* Request state */
58
+ public static readonly UNSENT = 0
59
+ public static readonly OPENED = 1
60
+ public static readonly HEADERS_RECEIVED = 2
61
+ public static readonly LOADING = 3
62
+ public static readonly DONE = 4
63
+ public readonly UNSENT = 0
64
+ public readonly OPENED = 1
65
+ public readonly HEADERS_RECEIVED = 2
66
+ public readonly LOADING = 3
67
+ public readonly DONE = 4
68
+
69
+ /* Custom public properties */
70
+ public method: string
71
+ public url: string
72
+
73
+ /* XHR public properties */
74
+ public withCredentials: boolean
75
+ public status: number
76
+ public statusText: string
77
+ public user?: string
78
+ public password?: string
79
+ public data: string
80
+ public async?: boolean
81
+ public response: any
82
+ public responseText: string
83
+ public responseType: XMLHttpRequestResponseType
84
+ public responseXML: Document | null
85
+ public responseURL: string
86
+ public upload: XMLHttpRequestUpload
87
+ public readyState: number
88
+ public onreadystatechange: (this: XMLHttpRequest, ev: Event) => any =
89
+ null as any
90
+ public timeout: number
91
+
92
+ /* Events */
93
+ public onabort: (
94
+ this: XMLHttpRequestEventTarget,
95
+ event: ProgressEvent
96
+ ) => any = null as any
97
+ public onerror: (this: XMLHttpRequestEventTarget, event: Event) => any =
98
+ null as any
99
+ public onload: (
100
+ this: XMLHttpRequestEventTarget,
101
+ event: ProgressEvent
102
+ ) => any = null as any
103
+ public onloadend: (
104
+ this: XMLHttpRequestEventTarget,
105
+ event: ProgressEvent
106
+ ) => any = null as any
107
+ public onloadstart: (
108
+ this: XMLHttpRequestEventTarget,
109
+ event: ProgressEvent
110
+ ) => any = null as any
111
+ public onprogress: (
112
+ this: XMLHttpRequestEventTarget,
113
+ event: ProgressEvent
114
+ ) => any = null as any
115
+ public ontimeout: (
116
+ this: XMLHttpRequestEventTarget,
117
+ event: ProgressEvent
118
+ ) => any = null as any
119
+
120
+ constructor() {
121
+ this.url = ''
122
+ this.method = 'GET'
123
+ this.readyState = this.UNSENT
124
+ this.withCredentials = false
125
+ this.status = 200
126
+ this.statusText = 'OK'
127
+ this.data = ''
128
+ this.response = ''
129
+ this.responseType = 'text'
130
+ this.responseText = ''
131
+ this.responseXML = null
132
+ this.responseURL = ''
133
+ this.upload = {} as any
134
+ this.timeout = 0
135
+
136
+ this._requestHeaders = new Headers()
137
+ this._responseHeaders = new Headers()
138
+ }
139
+
140
+ setReadyState(nextState: number): void {
141
+ if (nextState === this.readyState) {
142
+ return
143
+ }
144
+
145
+ debug('readyState change %d -> %d', this.readyState, nextState)
146
+ this.readyState = nextState
147
+
148
+ if (nextState !== this.UNSENT) {
149
+ debug('triggerring readystate change...')
150
+ this.trigger('readystatechange')
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Triggers both direct callback and attached event listeners
156
+ * for the given event.
157
+ */
158
+ trigger<
159
+ K extends keyof (XMLHttpRequestEventTargetEventMap & {
160
+ readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
161
+ })
162
+ >(eventName: K, options?: ProgressEventInit) {
163
+ debug('trigger "%s" (%d)', eventName, this.readyState)
164
+ debug('resolve listener for event "%s"', eventName)
165
+
166
+ // @ts-expect-error XMLHttpRequest class has no index signature.
167
+ const callback = this[`on${eventName}`] as XMLHttpRequestEventHandler
168
+ callback?.call(this, createEvent(this, eventName, options))
169
+
170
+ for (const event of this._events) {
171
+ if (event.name === eventName) {
172
+ debug(
173
+ 'calling mock event listener "%s" (%d)',
174
+ eventName,
175
+ this.readyState
176
+ )
177
+ event.listener.call(this, createEvent(this, eventName, options))
178
+ }
179
+ }
180
+
181
+ return this
182
+ }
183
+
184
+ reset() {
185
+ debug('reset')
186
+
187
+ this.setReadyState(this.UNSENT)
188
+ this.status = 200
189
+ this.statusText = 'OK'
190
+ this.data = ''
191
+ this.response = null as any
192
+ this.responseText = null as any
193
+ this.responseXML = null as any
194
+
195
+ this._requestHeaders = new Headers()
196
+ this._responseHeaders = new Headers()
197
+ }
198
+
199
+ public async open(
200
+ method: string,
201
+ url: string,
202
+ async: boolean = true,
203
+ user?: string,
204
+ password?: string
205
+ ) {
206
+ debug = createDebug(`XHR ${method} ${url}`)
207
+ debug('open', { method, url, async, user, password })
208
+
209
+ this.reset()
210
+ this.setReadyState(this.OPENED)
211
+
212
+ if (typeof url === 'undefined') {
213
+ this.url = method
214
+ this.method = 'GET'
215
+ } else {
216
+ this.url = url
217
+ this.method = method
218
+ this.async = async
219
+ this.user = user
220
+ this.password = password
221
+ }
222
+ }
223
+
224
+ public send(data?: string) {
225
+ debug('send %s %s', this.method, this.url)
226
+
227
+ this.data = data || ''
228
+
229
+ let url: URL
230
+
231
+ try {
232
+ url = new URL(this.url)
233
+ } catch (error) {
234
+ // Assume a relative URL, if construction of a new `URL` instance fails.
235
+ // Since `XMLHttpRequest` always executed in a DOM-like environment,
236
+ // resolve the relative request URL against the current window location.
237
+ url = new URL(this.url, window.location.href)
238
+ }
239
+
240
+ debug('request headers', this._requestHeaders)
241
+
242
+ // Create an intercepted request instance exposed to the request intercepting middleware.
243
+ const isoRequest: IsomorphicRequest = {
244
+ id: uuidv4(),
245
+ url,
246
+ method: this.method,
247
+ headers: this._requestHeaders,
248
+ credentials: this.withCredentials ? 'include' : 'omit',
249
+ body: this.data,
250
+ }
251
+
252
+ observer.emit('request', isoRequest)
253
+
254
+ debug('awaiting mocked response...')
255
+
256
+ Promise.resolve(until(async () => resolver(isoRequest, this))).then(
257
+ ([middlewareException, mockedResponse]) => {
258
+ // When the request middleware throws an exception, error the request.
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
+ )
265
+
266
+ // No way to propagate the actual error message.
267
+ this.trigger('error')
268
+ this.abort()
269
+
270
+ return
271
+ }
272
+
273
+ // Return a mocked response, if provided in the middleware.
274
+ if (mockedResponse) {
275
+ debug('received mocked response', mockedResponse)
276
+
277
+ // Trigger a loadstart event to indicate the initialization of the fetch.
278
+ this.trigger('loadstart')
279
+
280
+ this.status = mockedResponse.status || 200
281
+ this.statusText = mockedResponse.statusText || 'OK'
282
+ this._responseHeaders = mockedResponse.headers
283
+ ? objectToHeaders(mockedResponse.headers)
284
+ : new Headers()
285
+
286
+ debug('set response status', this.status, this.statusText)
287
+ debug('set response headers', this._responseHeaders)
288
+
289
+ // Mark that response headers has been received
290
+ // and trigger a ready state event to reflect received headers
291
+ // in a custom `onreadystatechange` callback.
292
+ this.setReadyState(this.HEADERS_RECEIVED)
293
+
294
+ debug('response type', this.responseType)
295
+ this.response = this.getResponseBody(mockedResponse.body)
296
+ this.responseText = mockedResponse.body || ''
297
+ this.responseXML = this.getResponseXML()
298
+
299
+ debug('set response body', this.response)
300
+
301
+ if (mockedResponse.body && this.response) {
302
+ this.setReadyState(this.LOADING)
303
+
304
+ // Presense of the mocked response implies a response body (not null).
305
+ // Presense of the coerced `this.response` implies the mocked body is valid.
306
+ const bodyBuffer = bufferFrom(mockedResponse.body)
307
+
308
+ // Trigger a progress event based on the mocked response body.
309
+ this.trigger('progress', {
310
+ loaded: bodyBuffer.length,
311
+ total: bodyBuffer.length,
312
+ })
313
+ }
314
+
315
+ /**
316
+ * Explicitly mark the request as done so its response never hangs.
317
+ * @see https://github.com/mswjs/interceptors/issues/13
318
+ */
319
+ this.setReadyState(this.DONE)
320
+
321
+ // Trigger a load event to indicate the fetch has succeeded.
322
+ this.trigger('load')
323
+ // Trigger a loadend event to indicate the fetch has completed.
324
+ this.trigger('loadend')
325
+
326
+ observer.emit('response', isoRequest, toIsoResponse(mockedResponse))
327
+ } else {
328
+ debug('no mocked response received!')
329
+
330
+ // Perform an original request, when the request middleware returned no mocked response.
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
340
+ )
341
+
342
+ // Reflect a successful state of the original request
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
+ })
386
+
387
+ // Assign callbacks and event listeners from the intercepted XHR instance
388
+ // to the original XHR instance.
389
+ this.propagateCallbacks(originalRequest)
390
+ this.propagateListeners(originalRequest)
391
+ this.propagateHeaders(originalRequest, this._requestHeaders)
392
+
393
+ if (this.async) {
394
+ originalRequest.timeout = this.timeout
395
+ }
396
+
397
+ debug('send', this.data)
398
+ originalRequest.send(this.data)
399
+ }
400
+ }
401
+ )
402
+ }
403
+
404
+ public abort() {
405
+ debug('abort')
406
+
407
+ if (this.readyState > this.UNSENT && this.readyState < this.DONE) {
408
+ this.setReadyState(this.UNSENT)
409
+ this.trigger('abort')
410
+ }
411
+ }
412
+
413
+ dispatchEvent() {
414
+ return false
415
+ }
416
+
417
+ public setRequestHeader(name: string, value: string) {
418
+ debug('set request header "%s" to "%s"', name, value)
419
+ this._requestHeaders.append(name, value)
420
+ }
421
+
422
+ public getResponseHeader(name: string): string | null {
423
+ debug('get response header "%s"', name)
424
+
425
+ if (this.readyState < this.HEADERS_RECEIVED) {
426
+ debug(
427
+ 'cannot return a header: headers not received (state: %s)',
428
+ this.readyState
429
+ )
430
+ return null
431
+ }
432
+
433
+ const headerValue = this._responseHeaders.get(name)
434
+
435
+ debug(
436
+ 'resolved response header "%s" to "%s"',
437
+ name,
438
+ headerValue,
439
+ this._responseHeaders
440
+ )
441
+
442
+ return headerValue
443
+ }
444
+
445
+ public getAllResponseHeaders(): string {
446
+ debug('get all response headers')
447
+
448
+ if (this.readyState < this.HEADERS_RECEIVED) {
449
+ debug(
450
+ 'cannot return headers: headers not received (state: %s)',
451
+ this.readyState
452
+ )
453
+ return ''
454
+ }
455
+
456
+ return headersToString(this._responseHeaders)
457
+ }
458
+
459
+ public addEventListener<
460
+ K extends keyof InternalXMLHttpRequestEventTargetEventMap
461
+ >(name: K, listener: XMLHttpRequestEventHandler) {
462
+ debug('addEventListener', name, listener)
463
+ this._events.push({
464
+ name,
465
+ listener,
466
+ })
467
+ }
468
+
469
+ public removeEventListener<K extends keyof XMLHttpRequestEventMap>(
470
+ name: K,
471
+ listener: (event?: XMLHttpRequestEventMap[K]) => void
472
+ ): void {
473
+ debug('removeEventListener', name, listener)
474
+ this._events = this._events.filter((storedEvent) => {
475
+ return storedEvent.name !== name && storedEvent.listener !== listener
476
+ })
477
+ }
478
+
479
+ public overrideMimeType() {}
480
+
481
+ /**
482
+ * Resolves the response based on the `responseType` value.
483
+ */
484
+ getResponseBody(body: string | undefined) {
485
+ // Handle an improperly set "null" value of the mocked response body.
486
+ const textBody = body ?? ''
487
+ debug('coerced response body to', textBody)
488
+
489
+ switch (this.responseType) {
490
+ case 'json': {
491
+ debug('resolving response body as JSON')
492
+ return parseJson(textBody)
493
+ }
494
+
495
+ case 'blob': {
496
+ const blobType =
497
+ this.getResponseHeader('content-type') || 'text/plain'
498
+ debug('resolving response body as Blob', { type: blobType })
499
+
500
+ return new Blob([textBody], {
501
+ type: blobType,
502
+ })
503
+ }
504
+
505
+ case 'arraybuffer': {
506
+ debug('resolving response body as ArrayBuffer')
507
+ const arrayBuffer = bufferFrom(textBody)
508
+ return arrayBuffer
509
+ }
510
+
511
+ default:
512
+ return textBody
513
+ }
514
+ }
515
+
516
+ getResponseXML() {
517
+ const contentType = this.getResponseHeader('Content-Type')
518
+ if (contentType === 'application/xml' || contentType === 'text/xml') {
519
+ return new DOMParser().parseFromString(this.responseText, contentType)
520
+ }
521
+ return null
522
+ }
523
+
524
+ /**
525
+ * Propagates mock XMLHttpRequest instance callbacks
526
+ * to the given XMLHttpRequest instance.
527
+ */
528
+ propagateCallbacks(request: XMLHttpRequest) {
529
+ request.onabort = this.abort
530
+ request.onerror = this.onerror
531
+ request.ontimeout = this.ontimeout
532
+ request.onload = this.onload
533
+ request.onloadstart = this.onloadstart
534
+ request.onloadend = this.onloadend
535
+ request.onprogress = this.onprogress
536
+ request.onreadystatechange = this.onreadystatechange
537
+ }
538
+
539
+ /**
540
+ * Propagates the mock XMLHttpRequest instance listeners
541
+ * to the given XMLHttpRequest instance.
542
+ */
543
+ propagateListeners(request: XMLHttpRequest) {
544
+ debug(
545
+ 'propagating request listeners (%d) to the original request',
546
+ this._events.length,
547
+ this._events
548
+ )
549
+
550
+ this._events.forEach(({ name, listener }) => {
551
+ request.addEventListener(name, listener)
552
+ })
553
+ }
554
+
555
+ propagateHeaders(request: XMLHttpRequest, headers: Headers) {
556
+ debug('propagating request headers to the original request', headers)
557
+
558
+ // Preserve the request headers casing.
559
+ Object.entries(headers.raw()).forEach(([name, value]) => {
560
+ debug('setting "%s" (%s) header on the original request', name, value)
561
+ request.setRequestHeader(name, value)
562
+ })
563
+ }
564
+ }
565
+ }
@@ -0,0 +1,34 @@
1
+ import { Interceptor } from '../../createInterceptor'
2
+ import { createXMLHttpRequestOverride } from './XMLHttpRequestOverride'
3
+
4
+ const debug = require('debug')('XHR')
5
+
6
+ const pureXMLHttpRequest =
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
10
+
11
+ /**
12
+ * Intercepts requests issued via `XMLHttpRequest`.
13
+ */
14
+ export const interceptXMLHttpRequest: Interceptor = (observer, resolver) => {
15
+ if (pureXMLHttpRequest) {
16
+ debug('patching "XMLHttpRequest" module...')
17
+
18
+ const XMLHttpRequestOverride = createXMLHttpRequestOverride({
19
+ pureXMLHttpRequest,
20
+ observer,
21
+ resolver,
22
+ })
23
+
24
+ window.XMLHttpRequest = XMLHttpRequestOverride
25
+ }
26
+
27
+ return () => {
28
+ if (pureXMLHttpRequest) {
29
+ debug('restoring modules...')
30
+
31
+ window.XMLHttpRequest = pureXMLHttpRequest
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,51 @@
1
+ export class EventPolyfill implements Event {
2
+ readonly AT_TARGET: number = 0
3
+ readonly BUBBLING_PHASE: number = 0
4
+ readonly CAPTURING_PHASE: number = 0
5
+ readonly NONE: number = 0
6
+
7
+ public type: string = ''
8
+ public srcElement: EventTarget | null = null
9
+ public target: EventTarget | null
10
+ public currentTarget: EventTarget | null = null
11
+ public eventPhase: number = 0
12
+ public timeStamp: number
13
+ public isTrusted: boolean = true
14
+ public composed: boolean = false
15
+ public cancelable: boolean = true
16
+ public defaultPrevented: boolean = false
17
+ public bubbles: boolean = true
18
+ public lengthComputable: boolean = true
19
+ public loaded: number = 0
20
+ public total: number = 0
21
+
22
+ cancelBubble: boolean = false
23
+ returnValue: boolean = true
24
+
25
+ constructor(
26
+ type: string,
27
+ options?: { target: EventTarget; currentTarget: EventTarget }
28
+ ) {
29
+ this.type = type
30
+ this.target = options?.target || null
31
+ this.currentTarget = options?.currentTarget || null
32
+ this.timeStamp = Date.now()
33
+ }
34
+
35
+ public composedPath(): EventTarget[] {
36
+ return []
37
+ }
38
+
39
+ public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {
40
+ this.type = type
41
+ this.bubbles = !!bubbles
42
+ this.cancelable = !!cancelable
43
+ }
44
+
45
+ public preventDefault() {
46
+ this.defaultPrevented = true
47
+ }
48
+
49
+ public stopPropagation() {}
50
+ public stopImmediatePropagation() {}
51
+ }
@@ -0,0 +1,17 @@
1
+ import { EventPolyfill } from './EventPolyfill'
2
+
3
+ export class ProgressEventPolyfill extends EventPolyfill {
4
+ readonly lengthComputable: boolean
5
+ readonly composed: boolean
6
+ readonly loaded: number
7
+ readonly total: number
8
+
9
+ constructor(type: string, init?: ProgressEventInit) {
10
+ super(type)
11
+
12
+ this.lengthComputable = init?.lengthComputable || false
13
+ this.composed = init?.composed || false
14
+ this.loaded = init?.loaded || 0
15
+ this.total = init?.total || 0
16
+ }
17
+ }