@mswjs/interceptors 0.34.1 → 0.34.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 (58) hide show
  1. package/lib/browser/{chunk-7A4UJNSW.mjs → chunk-73X2CAEH.mjs} +6 -6
  2. package/lib/browser/chunk-73X2CAEH.mjs.map +1 -0
  3. package/lib/browser/{chunk-ZXJSL23E.mjs → chunk-7BTAMQ4I.mjs} +119 -22
  4. package/lib/browser/chunk-7BTAMQ4I.mjs.map +1 -0
  5. package/lib/browser/{chunk-VRKVKT62.js → chunk-EJYZ4HR3.js} +6 -6
  6. package/lib/browser/chunk-EJYZ4HR3.js.map +1 -0
  7. package/lib/browser/{chunk-6MBJUL74.js → chunk-TPZUQHHY.js} +5 -5
  8. package/lib/browser/{chunk-NU2MPFD6.mjs → chunk-W7UG7MBM.mjs} +2 -2
  9. package/lib/browser/{chunk-LERABB57.js → chunk-YP3NI6UP.js} +121 -24
  10. package/lib/browser/chunk-YP3NI6UP.js.map +1 -0
  11. package/lib/browser/interceptors/XMLHttpRequest/index.js +3 -3
  12. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +2 -2
  13. package/lib/browser/interceptors/fetch/index.js +3 -3
  14. package/lib/browser/interceptors/fetch/index.mjs +2 -2
  15. package/lib/browser/presets/browser.js +5 -5
  16. package/lib/browser/presets/browser.mjs +3 -3
  17. package/lib/node/RemoteHttpInterceptor.js +7 -7
  18. package/lib/node/RemoteHttpInterceptor.mjs +3 -3
  19. package/lib/node/{chunk-RJGP7FQP.mjs → chunk-3ROKKA34.mjs} +119 -22
  20. package/lib/node/chunk-3ROKKA34.mjs.map +1 -0
  21. package/lib/node/{chunk-I57YSVAV.js → chunk-CQAXV6QM.js} +5 -5
  22. package/lib/node/{chunk-RTGLFNO3.mjs → chunk-GILG336Y.mjs} +2 -2
  23. package/lib/node/{chunk-T34TGCMR.js → chunk-MFGG7XIP.js} +121 -24
  24. package/lib/node/chunk-MFGG7XIP.js.map +1 -0
  25. package/lib/node/{chunk-5WWNCLB3.js → chunk-TQD7SQGP.js} +6 -6
  26. package/lib/node/chunk-TQD7SQGP.js.map +1 -0
  27. package/lib/node/{chunk-UN335ZTD.js → chunk-W4AQXISM.js} +5 -5
  28. package/lib/node/{chunk-DCMEIPZN.mjs → chunk-WCTY6JTP.mjs} +2 -2
  29. package/lib/node/{chunk-KY3RJ2M3.mjs → chunk-XSXCGXEY.mjs} +6 -6
  30. package/lib/node/chunk-XSXCGXEY.mjs.map +1 -0
  31. package/lib/node/interceptors/ClientRequest/index.js +3 -3
  32. package/lib/node/interceptors/ClientRequest/index.mjs +2 -2
  33. package/lib/node/interceptors/XMLHttpRequest/index.js +3 -3
  34. package/lib/node/interceptors/XMLHttpRequest/index.mjs +2 -2
  35. package/lib/node/interceptors/fetch/index.js +3 -3
  36. package/lib/node/interceptors/fetch/index.mjs +2 -2
  37. package/lib/node/presets/node.js +7 -7
  38. package/lib/node/presets/node.mjs +4 -4
  39. package/package.json +3 -1
  40. package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +159 -16
  41. package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +2 -2
  42. package/src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts +4 -4
  43. package/src/interceptors/XMLHttpRequest/utils/createEvent.ts +1 -1
  44. package/src/utils/handleRequest.ts +6 -6
  45. package/lib/browser/chunk-7A4UJNSW.mjs.map +0 -1
  46. package/lib/browser/chunk-LERABB57.js.map +0 -1
  47. package/lib/browser/chunk-VRKVKT62.js.map +0 -1
  48. package/lib/browser/chunk-ZXJSL23E.mjs.map +0 -1
  49. package/lib/node/chunk-5WWNCLB3.js.map +0 -1
  50. package/lib/node/chunk-KY3RJ2M3.mjs.map +0 -1
  51. package/lib/node/chunk-RJGP7FQP.mjs.map +0 -1
  52. package/lib/node/chunk-T34TGCMR.js.map +0 -1
  53. /package/lib/browser/{chunk-6MBJUL74.js.map → chunk-TPZUQHHY.js.map} +0 -0
  54. /package/lib/browser/{chunk-NU2MPFD6.mjs.map → chunk-W7UG7MBM.mjs.map} +0 -0
  55. /package/lib/node/{chunk-I57YSVAV.js.map → chunk-CQAXV6QM.js.map} +0 -0
  56. /package/lib/node/{chunk-RTGLFNO3.mjs.map → chunk-GILG336Y.mjs.map} +0 -0
  57. /package/lib/node/{chunk-UN335ZTD.js.map → chunk-W4AQXISM.js.map} +0 -0
  58. /package/lib/node/{chunk-DCMEIPZN.mjs.map → chunk-WCTY6JTP.mjs.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../../chunk-DCMEIPZN.mjs";
4
- import "../../chunk-KY3RJ2M3.mjs";
3
+ } from "../../chunk-WCTY6JTP.mjs";
4
+ import "../../chunk-XSXCGXEY.mjs";
5
5
  import "../../chunk-BUCULLYM.mjs";
6
6
  export {
7
7
  ClientRequestInterceptor
@@ -1,11 +1,11 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkT34TGCMRjs = require('../../chunk-T34TGCMR.js');
3
+ var _chunkMFGG7XIPjs = require('../../chunk-MFGG7XIP.js');
4
4
  require('../../chunk-LK6DILFK.js');
5
5
  require('../../chunk-IDEEMJ3F.js');
6
- require('../../chunk-5WWNCLB3.js');
6
+ require('../../chunk-TQD7SQGP.js');
7
7
  require('../../chunk-YGM3BCJU.js');
8
8
 
9
9
 
10
- exports.XMLHttpRequestInterceptor = _chunkT34TGCMRjs.XMLHttpRequestInterceptor;
10
+ exports.XMLHttpRequestInterceptor = _chunkMFGG7XIPjs.XMLHttpRequestInterceptor;
11
11
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  XMLHttpRequestInterceptor
3
- } from "../../chunk-RJGP7FQP.mjs";
3
+ } from "../../chunk-3ROKKA34.mjs";
4
4
  import "../../chunk-6HYIRFX2.mjs";
5
5
  import "../../chunk-BZ3Y7YV5.mjs";
6
- import "../../chunk-KY3RJ2M3.mjs";
6
+ import "../../chunk-XSXCGXEY.mjs";
7
7
  import "../../chunk-BUCULLYM.mjs";
8
8
  export {
9
9
  XMLHttpRequestInterceptor
@@ -1,10 +1,10 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkUN335ZTDjs = require('../../chunk-UN335ZTD.js');
3
+ var _chunkW4AQXISMjs = require('../../chunk-W4AQXISM.js');
4
4
  require('../../chunk-IDEEMJ3F.js');
5
- require('../../chunk-5WWNCLB3.js');
5
+ require('../../chunk-TQD7SQGP.js');
6
6
  require('../../chunk-YGM3BCJU.js');
7
7
 
8
8
 
9
- exports.FetchInterceptor = _chunkUN335ZTDjs.FetchInterceptor;
9
+ exports.FetchInterceptor = _chunkW4AQXISMjs.FetchInterceptor;
10
10
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  FetchInterceptor
3
- } from "../../chunk-RTGLFNO3.mjs";
3
+ } from "../../chunk-GILG336Y.mjs";
4
4
  import "../../chunk-BZ3Y7YV5.mjs";
5
- import "../../chunk-KY3RJ2M3.mjs";
5
+ import "../../chunk-XSXCGXEY.mjs";
6
6
  import "../../chunk-BUCULLYM.mjs";
7
7
  export {
8
8
  FetchInterceptor
@@ -1,22 +1,22 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkI57YSVAVjs = require('../chunk-I57YSVAV.js');
3
+ var _chunkCQAXV6QMjs = require('../chunk-CQAXV6QM.js');
4
4
 
5
5
 
6
- var _chunkT34TGCMRjs = require('../chunk-T34TGCMR.js');
6
+ var _chunkMFGG7XIPjs = require('../chunk-MFGG7XIP.js');
7
7
  require('../chunk-LK6DILFK.js');
8
8
 
9
9
 
10
- var _chunkUN335ZTDjs = require('../chunk-UN335ZTD.js');
10
+ var _chunkW4AQXISMjs = require('../chunk-W4AQXISM.js');
11
11
  require('../chunk-IDEEMJ3F.js');
12
- require('../chunk-5WWNCLB3.js');
12
+ require('../chunk-TQD7SQGP.js');
13
13
  require('../chunk-YGM3BCJU.js');
14
14
 
15
15
  // src/presets/node.ts
16
16
  var node_default = [
17
- new (0, _chunkI57YSVAVjs.ClientRequestInterceptor)(),
18
- new (0, _chunkT34TGCMRjs.XMLHttpRequestInterceptor)(),
19
- new (0, _chunkUN335ZTDjs.FetchInterceptor)()
17
+ new (0, _chunkCQAXV6QMjs.ClientRequestInterceptor)(),
18
+ new (0, _chunkMFGG7XIPjs.XMLHttpRequestInterceptor)(),
19
+ new (0, _chunkW4AQXISMjs.FetchInterceptor)()
20
20
  ];
21
21
 
22
22
 
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../chunk-DCMEIPZN.mjs";
3
+ } from "../chunk-WCTY6JTP.mjs";
4
4
  import {
5
5
  XMLHttpRequestInterceptor
6
- } from "../chunk-RJGP7FQP.mjs";
6
+ } from "../chunk-3ROKKA34.mjs";
7
7
  import "../chunk-6HYIRFX2.mjs";
8
8
  import {
9
9
  FetchInterceptor
10
- } from "../chunk-RTGLFNO3.mjs";
10
+ } from "../chunk-GILG336Y.mjs";
11
11
  import "../chunk-BZ3Y7YV5.mjs";
12
- import "../chunk-KY3RJ2M3.mjs";
12
+ import "../chunk-XSXCGXEY.mjs";
13
13
  import "../chunk-BUCULLYM.mjs";
14
14
 
15
15
  // src/presets/node.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mswjs/interceptors",
3
3
  "description": "Low-level HTTP/HTTPS/XHR/fetch request interception library.",
4
- "version": "0.34.1",
4
+ "version": "0.34.2",
5
5
  "main": "./lib/node/index.js",
6
6
  "module": "./lib/node/index.mjs",
7
7
  "types": "./lib/node/index.d.ts",
@@ -117,6 +117,7 @@
117
117
  "@playwright/test": "^1.37.1",
118
118
  "@types/cors": "^2.8.12",
119
119
  "@types/express": "^4.17.13",
120
+ "@types/express-fileupload": "^1.5.0",
120
121
  "@types/express-rate-limit": "^6.0.0",
121
122
  "@types/follow-redirects": "^1.14.1",
122
123
  "@types/jest": "^27.0.3",
@@ -132,6 +133,7 @@
132
133
  "cz-conventional-changelog": "3.3.0",
133
134
  "engine.io-parser": "^5.2.1",
134
135
  "express": "^4.17.3",
136
+ "express-fileupload": "^1.5.1",
135
137
  "express-rate-limit": "^6.3.0",
136
138
  "follow-redirects": "^1.15.1",
137
139
  "got": "^11.8.3",
@@ -48,9 +48,14 @@ export class XMLHttpRequestController {
48
48
  private requestBody?: XMLHttpRequestBodyInit | Document | null
49
49
  private responseBuffer: Uint8Array
50
50
  private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>
51
+ private uploadEvents: Map<
52
+ keyof XMLHttpRequestEventTargetEventMap,
53
+ Array<Function>
54
+ >
51
55
 
52
56
  constructor(readonly initialRequest: XMLHttpRequest, public logger: Logger) {
53
57
  this.events = new Map()
58
+ this.uploadEvents = new Map()
54
59
  this.requestId = createRequestId()
55
60
  this.requestHeaders = new Headers()
56
61
  this.responseBuffer = new Uint8Array()
@@ -200,6 +205,49 @@ export class XMLHttpRequestController {
200
205
  }
201
206
  },
202
207
  })
208
+
209
+ /**
210
+ * Proxy the `.upload` property to gather the event listeners/callbacks.
211
+ */
212
+ define(
213
+ this.request,
214
+ 'upload',
215
+ createProxy(this.request.upload, {
216
+ setProperty: ([propertyName, nextValue], invoke) => {
217
+ switch (propertyName) {
218
+ case 'onloadstart':
219
+ case 'onprogress':
220
+ case 'onaboart':
221
+ case 'onerror':
222
+ case 'onload':
223
+ case 'ontimeout':
224
+ case 'onloadend': {
225
+ const eventName = propertyName.slice(
226
+ 2
227
+ ) as keyof XMLHttpRequestEventTargetEventMap
228
+
229
+ this.registerUploadEvent(eventName, nextValue as Function)
230
+ }
231
+ }
232
+
233
+ return invoke()
234
+ },
235
+ methodCall: ([methodName, args], invoke) => {
236
+ switch (methodName) {
237
+ case 'addEventListener': {
238
+ const [eventName, listener] = args as [
239
+ keyof XMLHttpRequestEventTargetEventMap,
240
+ Function
241
+ ]
242
+ this.registerUploadEvent(eventName, listener)
243
+ this.logger.info('upload.addEventListener', eventName, listener)
244
+
245
+ return invoke()
246
+ }
247
+ }
248
+ },
249
+ })
250
+ )
203
251
  }
204
252
 
205
253
  private registerEvent(
@@ -213,11 +261,52 @@ export class XMLHttpRequestController {
213
261
  this.logger.info('registered event "%s"', eventName, listener)
214
262
  }
215
263
 
264
+ private registerUploadEvent(
265
+ eventName: keyof XMLHttpRequestEventTargetEventMap,
266
+ listener: Function
267
+ ): void {
268
+ const prevEvents = this.uploadEvents.get(eventName) || []
269
+ const nextEvents = prevEvents.concat(listener)
270
+ this.uploadEvents.set(eventName, nextEvents)
271
+
272
+ this.logger.info('registered upload event "%s"', eventName, listener)
273
+ }
274
+
216
275
  /**
217
276
  * Responds to the current request with the given
218
277
  * Fetch API `Response` instance.
219
278
  */
220
- public respondWith(response: Response): void {
279
+ public async respondWith(response: Response): Promise<void> {
280
+ /**
281
+ * Dispatch request upload events for requests with a body.
282
+ * @see https://github.com/mswjs/interceptors/issues/573
283
+ */
284
+ if (this.requestBody != null) {
285
+ const totalRequestBodyLength = this.requestHeaders.has('content-length')
286
+ ? Number(this.requestHeaders.get('content-length'))
287
+ : await getXMLHttpRequestBodyInitLength(
288
+ this.requestBody,
289
+ this.requestHeaders
290
+ )
291
+
292
+ this.trigger('loadstart', this.request.upload, {
293
+ loaded: 0,
294
+ total: totalRequestBodyLength,
295
+ })
296
+ this.trigger('progress', this.request.upload, {
297
+ loaded: totalRequestBodyLength,
298
+ total: totalRequestBodyLength,
299
+ })
300
+ this.trigger('load', this.request.upload, {
301
+ loaded: totalRequestBodyLength,
302
+ total: totalRequestBodyLength,
303
+ })
304
+ this.trigger('loadend', this.request.upload, {
305
+ loaded: totalRequestBodyLength,
306
+ total: totalRequestBodyLength,
307
+ })
308
+ }
309
+
221
310
  this.logger.info(
222
311
  'responding with a mocked response: %d %s',
223
312
  response.status,
@@ -303,7 +392,7 @@ export class XMLHttpRequestController {
303
392
  },
304
393
  })
305
394
 
306
- const totalResponseBodyLength = response.headers.has('Content-Length')
395
+ const totalResponseBodyLength = response.headers.has('content-length')
307
396
  ? Number(response.headers.get('Content-Length'))
308
397
  : /**
309
398
  * @todo Infer the response body length from the response body.
@@ -312,7 +401,7 @@ export class XMLHttpRequestController {
312
401
 
313
402
  this.logger.info('calculated response body length', totalResponseBodyLength)
314
403
 
315
- this.trigger('loadstart', {
404
+ this.trigger('loadstart', this.request, {
316
405
  loaded: 0,
317
406
  total: totalResponseBodyLength,
318
407
  })
@@ -325,12 +414,12 @@ export class XMLHttpRequestController {
325
414
 
326
415
  this.setReadyState(this.request.DONE)
327
416
 
328
- this.trigger('load', {
417
+ this.trigger('load', this.request, {
329
418
  loaded: this.responseBuffer.byteLength,
330
419
  total: totalResponseBodyLength,
331
420
  })
332
421
 
333
- this.trigger('loadend', {
422
+ this.trigger('loadend', this.request, {
334
423
  loaded: this.responseBuffer.byteLength,
335
424
  total: totalResponseBodyLength,
336
425
  })
@@ -354,7 +443,7 @@ export class XMLHttpRequestController {
354
443
  this.logger.info('read response body chunk:', value)
355
444
  this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)
356
445
 
357
- this.trigger('progress', {
446
+ this.trigger('progress', this.request, {
358
447
  loaded: this.responseBuffer.byteLength,
359
448
  total: totalResponseBodyLength,
360
449
  })
@@ -485,8 +574,8 @@ export class XMLHttpRequestController {
485
574
  this.logger.info('responding with an error')
486
575
 
487
576
  this.setReadyState(this.request.DONE)
488
- this.trigger('error')
489
- this.trigger('loadend')
577
+ this.trigger('error', this.request)
578
+ this.trigger('loadend', this.request)
490
579
  }
491
580
 
492
581
  /**
@@ -511,7 +600,7 @@ export class XMLHttpRequestController {
511
600
  if (nextReadyState !== this.request.UNSENT) {
512
601
  this.logger.info('triggerring "readystatechange" event...')
513
602
 
514
- this.trigger('readystatechange')
603
+ this.trigger('readystatechange', this.request)
515
604
  }
516
605
  }
517
606
 
@@ -522,20 +611,27 @@ export class XMLHttpRequestController {
522
611
  EventName extends keyof (XMLHttpRequestEventTargetEventMap & {
523
612
  readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
524
613
  })
525
- >(eventName: EventName, options?: ProgressEventInit): void {
526
- const callback = this.request[`on${eventName}`]
527
- const event = createEvent(this.request, eventName, options)
614
+ >(
615
+ eventName: EventName,
616
+ target: XMLHttpRequest | XMLHttpRequestUpload,
617
+ options?: ProgressEventInit
618
+ ): void {
619
+ const callback = (target as XMLHttpRequest)[`on${eventName}`]
620
+ const event = createEvent(target, eventName, options)
528
621
 
529
622
  this.logger.info('trigger "%s"', eventName, options || '')
530
623
 
531
624
  // Invoke direct callbacks.
532
625
  if (typeof callback === 'function') {
533
626
  this.logger.info('found a direct "%s" callback, calling...', eventName)
534
- callback.call(this.request, event)
627
+ callback.call(target as XMLHttpRequest, event)
535
628
  }
536
629
 
537
630
  // Invoke event listeners.
538
- for (const [registeredEventName, listeners] of this.events) {
631
+ const events =
632
+ target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events
633
+
634
+ for (const [registeredEventName, listeners] of events) {
539
635
  if (registeredEventName === eventName) {
540
636
  this.logger.info(
541
637
  'found %d listener(s) for "%s" event, calling...',
@@ -543,7 +639,7 @@ export class XMLHttpRequestController {
543
639
  eventName
544
640
  )
545
641
 
546
- listeners.forEach((listener) => listener.call(this.request, event))
642
+ listeners.forEach((listener) => listener.call(target, event))
547
643
  }
548
644
  }
549
645
  }
@@ -551,7 +647,7 @@ export class XMLHttpRequestController {
551
647
  /**
552
648
  * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.
553
649
  */
554
- public toFetchApiRequest(): Request {
650
+ private toFetchApiRequest(): Request {
555
651
  this.logger.info('converting request to a Fetch API Request...')
556
652
 
557
653
  const fetchRequest = new Request(this.url.href, {
@@ -626,3 +722,50 @@ function define(
626
722
  value,
627
723
  })
628
724
  }
725
+
726
+ async function getXMLHttpRequestBodyInitLength(
727
+ body: XMLHttpRequestBodyInit | Document,
728
+ headers: Headers
729
+ ): Promise<number> {
730
+ if (typeof body === 'object' && 'byteLength' in body) {
731
+ return body.byteLength
732
+ }
733
+
734
+ if (body instanceof Blob) {
735
+ return body.size
736
+ }
737
+
738
+ if (body instanceof FormData) {
739
+ const lines: Array<string> = []
740
+ const contentType =
741
+ headers.get('content-type') || 'application/octet-stream'
742
+
743
+ for (const [name, entry] of body) {
744
+ lines.push(`------WebKitFormBoundary1234567890123456`)
745
+ lines.push(`content-type: ${contentType}`)
746
+
747
+ if (typeof entry === 'string') {
748
+ lines.push(`content-disposition: form-data; name="${name}"`)
749
+ lines.push(``)
750
+ lines.push(entry)
751
+ } else {
752
+ lines.push(
753
+ `content-disposition: form-data; name="${name}"; filename="${entry.name}"`
754
+ )
755
+ lines.push(``)
756
+ lines.push(await entry.text())
757
+ }
758
+ }
759
+
760
+ lines.push('------WebKitFormBoundary1234567890123456--')
761
+ lines.push(``)
762
+
763
+ return lines.join('\r\n').length
764
+ }
765
+
766
+ if (body instanceof Document) {
767
+ return body.documentElement.innerHTML.length
768
+ }
769
+
770
+ return body.toString().length
771
+ }
@@ -66,8 +66,8 @@ export function createXMLHttpRequestProxy({
66
66
  requestId,
67
67
  controller,
68
68
  emitter,
69
- onResponse: (response) => {
70
- this.respondWith(response)
69
+ onResponse: async (response) => {
70
+ await this.respondWith(response)
71
71
  },
72
72
  onRequestError: () => {
73
73
  this.errorWith(new TypeError('Network error'))
@@ -1,8 +1,8 @@
1
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
2
+ readonly NONE = 0
3
+ readonly CAPTURING_PHASE = 1
4
+ readonly AT_TARGET = 2
5
+ readonly BUBBLING_PHASE = 3
6
6
 
7
7
  public type: string = ''
8
8
  public srcElement: EventTarget | null = null
@@ -4,7 +4,7 @@ import { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'
4
4
  const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'
5
5
 
6
6
  export function createEvent(
7
- target: XMLHttpRequest,
7
+ target: XMLHttpRequest | XMLHttpRequestUpload,
8
8
  type: string,
9
9
  init?: ProgressEventInit
10
10
  ): EventPolyfill {
@@ -22,7 +22,7 @@ interface HandleRequestOptions {
22
22
  * Called when the request has been handled
23
23
  * with the given `Response` instance.
24
24
  */
25
- onResponse: (response: Response) => void
25
+ onResponse: (response: Response) => void | Promise<void>
26
26
 
27
27
  /**
28
28
  * Called when the request has been handled
@@ -43,7 +43,7 @@ interface HandleRequestOptions {
43
43
  export async function handleRequest(
44
44
  options: HandleRequestOptions
45
45
  ): Promise<boolean> {
46
- const handleResponse = (response: Response | Error): true => {
46
+ const handleResponse = async (response: Response | Error) => {
47
47
  if (response instanceof Error) {
48
48
  options.onError(response)
49
49
  }
@@ -52,13 +52,13 @@ export async function handleRequest(
52
52
  else if (isResponseError(response)) {
53
53
  options.onRequestError(response)
54
54
  } else {
55
- options.onResponse(response)
55
+ await options.onResponse(response)
56
56
  }
57
57
 
58
58
  return true
59
59
  }
60
60
 
61
- const handleResponseError = (error: unknown): boolean => {
61
+ const handleResponseError = async (error: unknown): Promise<boolean> => {
62
62
  // Forward the special interceptor error instances
63
63
  // to the developer. These must not be handled in any way.
64
64
  if (error instanceof InterceptorError) {
@@ -73,7 +73,7 @@ export async function handleRequest(
73
73
 
74
74
  // Handle thrown responses.
75
75
  if (error instanceof Response) {
76
- return handleResponse(error)
76
+ return await handleResponse(error)
77
77
  }
78
78
 
79
79
  return false
@@ -140,7 +140,7 @@ export async function handleRequest(
140
140
  if (result.error) {
141
141
  // Handle the error during the request listener execution.
142
142
  // These can be thrown responses or request errors.
143
- if (handleResponseError(result.error)) {
143
+ if (await handleResponseError(result.error)) {
144
144
  return true
145
145
  }
146
146
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/RequestController.ts","../../src/InterceptorError.ts","../../src/utils/emitAsync.ts","../../src/utils/handleRequest.ts","../../src/utils/isNodeLikeError.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { InterceptorError } from './InterceptorError'\n\nconst kRequestHandled = Symbol('kRequestHandled')\nexport const kResponsePromise = Symbol('kResponsePromise')\n\nexport class RequestController {\n /**\n * Internal response promise.\n * Available only for the library internals to grab the\n * response instance provided by the developer.\n * @note This promise cannot be rejected. It's either infinitely\n * pending or resolved with whichever Response was passed to `respondWith()`.\n */\n [kResponsePromise]: DeferredPromise<Response | Error | undefined>;\n\n /**\n * Internal flag indicating if this request has been handled.\n * @note The response promise becomes \"fulfilled\" on the next tick.\n */\n [kRequestHandled]: boolean\n\n constructor(private request: Request) {\n this[kRequestHandled] = false\n this[kResponsePromise] = new DeferredPromise()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n !this[kRequestHandled],\n 'Failed to respond to the \"%s %s\" request: the \"request\" event has already been handled.',\n this.request.method,\n this.request.url\n )\n\n this[kRequestHandled] = true\n this[kResponsePromise].resolve(response)\n\n /**\n * @note The request conrtoller doesn't do anything\n * apart from letting the interceptor await the response\n * provided by the developer through the response promise.\n * Each interceptor implements the actual respondWith/errorWith\n * logic based on that interceptor's needs.\n */\n }\n\n /**\n * Error this request with the given error.\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n */\n public errorWith(error?: Error): void {\n invariant.as(\n InterceptorError,\n !this[kRequestHandled],\n 'Failed to error the \"%s %s\" request: the \"request\" event has already been handled.',\n this.request.method,\n this.request.url\n )\n\n this[kRequestHandled] = true\n\n /**\n * @note Resolve the response promise, not reject.\n * This helps us differentiate between unhandled exceptions\n * and intended errors (\"errorWith\") while waiting for the response.\n */\n this[kResponsePromise].resolve(error)\n }\n}\n","export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listners = emitter.listeners(eventName)\n\n if (listners.length === 0) {\n return\n }\n\n for (const listener of listners) {\n await listener.apply(emitter, data)\n }\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { kResponsePromise, RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n ResponseError,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n\n /**\n * Called when the request has been handled\n * with the given `Response` instance.\n */\n onResponse: (response: Response) => void\n\n /**\n * Called when the request has been handled\n * with the given `Response.error()` instance.\n */\n onRequestError: (response: ResponseError) => void\n\n /**\n * Called when an unhandled error happens during the\n * request handling. This is never a thrown error/response.\n */\n onError: (error: unknown) => void\n}\n\n/**\n * @returns {Promise<boolean>} Indicates whether the request has been handled.\n */\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<boolean> {\n const handleResponse = (response: Response | Error): true => {\n if (response instanceof Error) {\n options.onError(response)\n }\n\n // Handle \"Response.error()\" instances.\n else if (isResponseError(response)) {\n options.onRequestError(response)\n } else {\n options.onResponse(response)\n }\n\n return true\n }\n\n const handleResponseError = (error: unknown): boolean => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n options.onError(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n options.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== options.requestId) {\n return\n }\n\n if (options.controller[kResponsePromise].state === 'pending') {\n options.controller[kResponsePromise].resolve(undefined)\n }\n })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListtenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListtenersPromise,\n options.controller[kResponsePromise],\n ])\n\n // The response promise will settle immediately once\n // the developer calls either \"respondWith\" or \"errorWith\".\n const mockedResponse = await options.controller[kResponsePromise]\n return mockedResponse\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n options.onError(requestAbortPromise.rejectionReason)\n return true\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (handleResponseError(result.error)) {\n return true\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n }).then(() => {\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the response in any way, preemptively\n // resolve the pending response promise from the new controller.\n // This prevents it from hanging forever.\n if (\n unhandledExceptionController[kResponsePromise].state === 'pending'\n ) {\n unhandledExceptionController[kResponsePromise].resolve(undefined)\n }\n })\n\n const nextResult = await until(\n () => unhandledExceptionController[kResponsePromise]\n )\n\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n if (nextResult.error) {\n return handleResponseError(nextResult.error)\n }\n\n if (nextResult.data) {\n return handleResponse(nextResult.data)\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n options.onResponse(createServerErrorResponse(result.error))\n return true\n }\n\n /**\n * Handle a mocked Response instance.\n * @note That this can also be an Error in case\n * the developer called \"errorWith\". This differentiates\n * unhandled exceptions from intended errors.\n */\n if (result.data) {\n return handleResponse(result.data)\n }\n\n // In all other cases, consider the request unhandled.\n // The interceptor must perform it as-is.\n return false\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n"],"mappings":"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","names":["DeferredPromise","DeferredPromise"]}