@mswjs/interceptors 0.28.4 → 0.29.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.
Files changed (77) hide show
  1. package/lib/browser/{chunk-LR5DGYDZ.js → chunk-2CRB3JAQ.js} +1 -1
  2. package/lib/browser/chunk-2CRB3JAQ.js.map +1 -0
  3. package/lib/browser/{chunk-FIBZ2AT2.mjs → chunk-732REFPX.mjs} +16 -2
  4. package/lib/browser/chunk-732REFPX.mjs.map +1 -0
  5. package/lib/browser/{chunk-ZQVP33QW.mjs → chunk-MAEPOYB6.mjs} +62 -24
  6. package/lib/browser/chunk-MAEPOYB6.mjs.map +1 -0
  7. package/lib/browser/{chunk-6JZQ6WGN.mjs → chunk-OMISYKWR.mjs} +1 -1
  8. package/lib/browser/chunk-OMISYKWR.mjs.map +1 -0
  9. package/lib/browser/{chunk-WKPHOWK2.js → chunk-PSX5J3RF.js} +23 -9
  10. package/lib/browser/chunk-PSX5J3RF.js.map +1 -0
  11. package/lib/browser/{chunk-5Q77IUWF.js → chunk-WBHIW62P.js} +67 -29
  12. package/lib/browser/chunk-WBHIW62P.js.map +1 -0
  13. package/lib/browser/{glossary-640c9679.d.ts → glossary-1c204f45.d.ts} +11 -0
  14. package/lib/browser/index.d.ts +1 -1
  15. package/lib/browser/index.js +2 -2
  16. package/lib/browser/index.mjs +1 -1
  17. package/lib/browser/interceptors/XMLHttpRequest/index.d.ts +1 -1
  18. package/lib/browser/interceptors/XMLHttpRequest/index.js +3 -3
  19. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +2 -2
  20. package/lib/browser/interceptors/fetch/index.d.ts +1 -1
  21. package/lib/browser/interceptors/fetch/index.js +3 -3
  22. package/lib/browser/interceptors/fetch/index.mjs +2 -2
  23. package/lib/browser/presets/browser.d.ts +1 -1
  24. package/lib/browser/presets/browser.js +5 -5
  25. package/lib/browser/presets/browser.mjs +3 -3
  26. package/lib/node/{BatchInterceptor-cb145daa.d.ts → BatchInterceptor-2badedde.d.ts} +1 -1
  27. package/lib/node/{Interceptor-6696a18d.d.ts → Interceptor-88ee47c0.d.ts} +11 -0
  28. package/lib/node/RemoteHttpInterceptor.d.ts +2 -2
  29. package/lib/node/RemoteHttpInterceptor.js +5 -5
  30. package/lib/node/RemoteHttpInterceptor.mjs +3 -3
  31. package/lib/node/{chunk-KHEPLFR3.js → chunk-APT7KA3B.js} +19 -5
  32. package/lib/node/chunk-APT7KA3B.js.map +1 -0
  33. package/lib/node/{chunk-FZJKKO5H.js → chunk-EIBTX65O.js} +1 -1
  34. package/lib/node/{chunk-FZJKKO5H.js.map → chunk-EIBTX65O.js.map} +1 -1
  35. package/lib/node/{chunk-UUKG6TUD.js → chunk-HAIWBQD5.js} +30 -28
  36. package/lib/node/chunk-HAIWBQD5.js.map +1 -0
  37. package/lib/node/{chunk-TCATD46T.mjs → chunk-JMNEFEYU.mjs} +30 -28
  38. package/lib/node/chunk-JMNEFEYU.mjs.map +1 -0
  39. package/lib/node/{chunk-OXXEWTTM.mjs → chunk-KSHIDGUL.mjs} +16 -2
  40. package/lib/node/chunk-KSHIDGUL.mjs.map +1 -0
  41. package/lib/node/{chunk-HAGW22AN.mjs → chunk-OJ6O4LSC.mjs} +1 -1
  42. package/lib/node/{chunk-HAGW22AN.mjs.map → chunk-OJ6O4LSC.mjs.map} +1 -1
  43. package/lib/node/index.d.ts +2 -2
  44. package/lib/node/index.js +2 -2
  45. package/lib/node/index.mjs +1 -1
  46. package/lib/node/interceptors/ClientRequest/index.d.ts +1 -1
  47. package/lib/node/interceptors/ClientRequest/index.js +2 -2
  48. package/lib/node/interceptors/ClientRequest/index.mjs +1 -1
  49. package/lib/node/interceptors/XMLHttpRequest/index.d.ts +1 -1
  50. package/lib/node/interceptors/XMLHttpRequest/index.js +3 -3
  51. package/lib/node/interceptors/XMLHttpRequest/index.mjs +2 -2
  52. package/lib/node/interceptors/fetch/index.d.ts +1 -1
  53. package/lib/node/interceptors/fetch/index.js +64 -26
  54. package/lib/node/interceptors/fetch/index.js.map +1 -1
  55. package/lib/node/interceptors/fetch/index.mjs +61 -23
  56. package/lib/node/interceptors/fetch/index.mjs.map +1 -1
  57. package/lib/node/presets/node.d.ts +1 -1
  58. package/lib/node/presets/node.js +5 -5
  59. package/lib/node/presets/node.mjs +3 -3
  60. package/package.json +1 -1
  61. package/src/glossary.ts +11 -0
  62. package/src/interceptors/ClientRequest/NodeClientRequest.ts +28 -4
  63. package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +4 -7
  64. package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +25 -0
  65. package/src/interceptors/fetch/index.ts +79 -29
  66. package/src/utils/getUrlByRequestOptions.test.ts +31 -3
  67. package/src/utils/getUrlByRequestOptions.ts +14 -38
  68. package/lib/browser/chunk-5Q77IUWF.js.map +0 -1
  69. package/lib/browser/chunk-6JZQ6WGN.mjs.map +0 -1
  70. package/lib/browser/chunk-FIBZ2AT2.mjs.map +0 -1
  71. package/lib/browser/chunk-LR5DGYDZ.js.map +0 -1
  72. package/lib/browser/chunk-WKPHOWK2.js.map +0 -1
  73. package/lib/browser/chunk-ZQVP33QW.mjs.map +0 -1
  74. package/lib/node/chunk-KHEPLFR3.js.map +0 -1
  75. package/lib/node/chunk-OXXEWTTM.mjs.map +0 -1
  76. package/lib/node/chunk-TCATD46T.mjs.map +0 -1
  77. package/lib/node/chunk-UUKG6TUD.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkFZJKKO5Hjs = require('../../chunk-FZJKKO5H.js');
3
+ var _chunkEIBTX65Ojs = require('../../chunk-EIBTX65O.js');
4
4
 
5
5
 
6
6
 
@@ -38,7 +38,7 @@ var _FetchInterceptor = class extends _chunkE4AC7YACjs.Interceptor {
38
38
  async setup() {
39
39
  const pureFetch = globalThis.fetch;
40
40
  _outvariant.invariant.call(void 0,
41
- !pureFetch[_chunkFZJKKO5Hjs.IS_PATCHED_MODULE],
41
+ !pureFetch[_chunkEIBTX65Ojs.IS_PATCHED_MODULE],
42
42
  'Failed to patch the "fetch" module: already patched.'
43
43
  );
44
44
  globalThis.fetch = async (input, init) => {
@@ -72,21 +72,29 @@ var _FetchInterceptor = class extends _chunkE4AC7YACjs.Interceptor {
72
72
  { once: true }
73
73
  );
74
74
  }
75
+ const responsePromise = new (0, _deferredpromise.DeferredPromise)();
75
76
  const respondWith = (response) => {
76
- const responseClone = response.clone();
77
- this.emitter.emit("response", {
78
- response: responseClone,
79
- isMockedResponse: true,
80
- request: interactiveRequest,
81
- requestId
82
- });
77
+ this.logger.info("responding with a mock response:", response);
78
+ if (this.emitter.listenerCount("response") > 0) {
79
+ this.logger.info('emitting the "response" event...');
80
+ const responseClone = response.clone();
81
+ this.emitter.emit("response", {
82
+ response: responseClone,
83
+ isMockedResponse: true,
84
+ request: interactiveRequest,
85
+ requestId
86
+ });
87
+ }
83
88
  Object.defineProperty(response, "url", {
84
89
  writable: false,
85
90
  enumerable: true,
86
91
  configurable: false,
87
92
  value: request.url
88
93
  });
89
- return response;
94
+ responsePromise.resolve(response);
95
+ };
96
+ const errorWith = (reason) => {
97
+ responsePromise.reject(reason);
90
98
  };
91
99
  const resolverResult = await _until.until.call(void 0,
92
100
  async () => {
@@ -109,16 +117,41 @@ var _FetchInterceptor = class extends _chunkE4AC7YACjs.Interceptor {
109
117
  }
110
118
  );
111
119
  if (requestAborted.state === "rejected") {
112
- return Promise.reject(requestAborted.rejectionReason);
120
+ this.logger.info(
121
+ "request has been aborted:",
122
+ requestAborted.rejectionReason
123
+ );
124
+ responsePromise.reject(requestAborted.rejectionReason);
125
+ return responsePromise;
113
126
  }
114
127
  if (resolverResult.error) {
128
+ this.logger.info(
129
+ "request listerner threw an error:",
130
+ resolverResult.error
131
+ );
115
132
  if (resolverResult.error instanceof Response) {
116
133
  if (_chunkE4AC7YACjs.isResponseError.call(void 0, resolverResult.error)) {
117
- return Promise.reject(createNetworkError(resolverResult.error));
134
+ errorWith(createNetworkError(resolverResult.error));
135
+ } else {
136
+ respondWith(resolverResult.error);
118
137
  }
119
- return respondWith(resolverResult.error);
120
138
  }
121
- return _chunkE4AC7YACjs.createServerErrorResponse.call(void 0, resolverResult.error);
139
+ if (this.emitter.listenerCount("unhandledException") > 0) {
140
+ await _chunkMQJ3JOOKjs.emitAsync.call(void 0, this.emitter, "unhandledException", {
141
+ error: resolverResult.error,
142
+ request,
143
+ requestId,
144
+ controller: {
145
+ respondWith,
146
+ errorWith
147
+ }
148
+ });
149
+ if (responsePromise.state !== "pending") {
150
+ return responsePromise;
151
+ }
152
+ }
153
+ respondWith(_chunkE4AC7YACjs.createServerErrorResponse.call(void 0, resolverResult.error));
154
+ return responsePromise;
122
155
  }
123
156
  const mockedResponse = resolverResult.data;
124
157
  if (mockedResponse && !((_a = request.signal) == null ? void 0 : _a.aborted)) {
@@ -127,30 +160,35 @@ var _FetchInterceptor = class extends _chunkE4AC7YACjs.Interceptor {
127
160
  this.logger.info(
128
161
  "received a network error response, rejecting the request promise..."
129
162
  );
130
- return Promise.reject(createNetworkError(mockedResponse));
163
+ errorWith(createNetworkError(mockedResponse));
164
+ } else {
165
+ respondWith(mockedResponse);
131
166
  }
132
- return respondWith(mockedResponse);
167
+ return responsePromise;
133
168
  }
134
169
  this.logger.info("no mocked response received!");
135
170
  return pureFetch(request).then((response) => {
136
- const responseClone = response.clone();
137
- this.logger.info("original fetch performed", responseClone);
138
- this.emitter.emit("response", {
139
- response: responseClone,
140
- isMockedResponse: false,
141
- request: interactiveRequest,
142
- requestId
143
- });
171
+ this.logger.info("original fetch performed", response);
172
+ if (this.emitter.listenerCount("response") > 0) {
173
+ this.logger.info('emitting the "response" event...');
174
+ const responseClone = response.clone();
175
+ this.emitter.emit("response", {
176
+ response: responseClone,
177
+ isMockedResponse: false,
178
+ request: interactiveRequest,
179
+ requestId
180
+ });
181
+ }
144
182
  return response;
145
183
  });
146
184
  };
147
- Object.defineProperty(globalThis.fetch, _chunkFZJKKO5Hjs.IS_PATCHED_MODULE, {
185
+ Object.defineProperty(globalThis.fetch, _chunkEIBTX65Ojs.IS_PATCHED_MODULE, {
148
186
  enumerable: true,
149
187
  configurable: true,
150
188
  value: true
151
189
  });
152
190
  this.subscriptions.push(() => {
153
- Object.defineProperty(globalThis.fetch, _chunkFZJKKO5Hjs.IS_PATCHED_MODULE, {
191
+ Object.defineProperty(globalThis.fetch, _chunkEIBTX65Ojs.IS_PATCHED_MODULE, {
154
192
  value: void 0
155
193
  });
156
194
  globalThis.fetch = pureFetch;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"names":["mockedResponse"],"mappings":"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","sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport {\n createServerErrorResponse,\n isResponseError,\n} from '../../utils/responseUtils'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const respondWith = (response: Response): Response => {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return response\n }\n\n const resolverResult = await until<unknown, Response | undefined>(\n async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n }\n )\n\n if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n // Treat thrown Responses as mocked responses.\n if (resolverResult.error instanceof Response) {\n // Treat thrown Response.error() as a request error.\n if (isResponseError(resolverResult.error)) {\n return Promise.reject(createNetworkError(resolverResult.error))\n }\n\n // Treat the rest of thrown Responses as mocked responses.\n return respondWith(resolverResult.error)\n }\n\n // Unhandled exceptions in the request listeners are\n // synonymous to unhandled exceptions on the server.\n // Those are represented as 500 error responses.\n return createServerErrorResponse(resolverResult.error)\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (isResponseError(mockedResponse)) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This differs from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n return Promise.reject(createNetworkError(mockedResponse))\n }\n\n return respondWith(mockedResponse)\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n const responseClone = response.clone()\n this.logger.info('original fetch performed', responseClone)\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n"]}
1
+ {"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"names":["mockedResponse"],"mappings":"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","sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport {\n createServerErrorResponse,\n isResponseError,\n} from '../../utils/responseUtils'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const respondWith = (response: Response): void => {\n this.logger.info('responding with a mock response:', response)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n }\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n responsePromise.resolve(response)\n }\n\n const errorWith = (reason: unknown): void => {\n responsePromise.reject(reason)\n }\n\n const resolverResult = await until<unknown, Response | undefined>(\n async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n }\n )\n\n if (requestAborted.state === 'rejected') {\n this.logger.info(\n 'request has been aborted:',\n requestAborted.rejectionReason\n )\n\n responsePromise.reject(requestAborted.rejectionReason)\n return responsePromise\n }\n\n if (resolverResult.error) {\n this.logger.info(\n 'request listerner threw an error:',\n resolverResult.error\n )\n\n // Treat thrown Responses as mocked responses.\n if (resolverResult.error instanceof Response) {\n // Treat thrown Response.error() as a request error.\n if (isResponseError(resolverResult.error)) {\n errorWith(createNetworkError(resolverResult.error))\n } else {\n // Treat the rest of thrown Responses as mocked responses.\n respondWith(resolverResult.error)\n }\n }\n\n // Emit the \"unhandledException\" interceptor event so the client\n // can opt-out from exceptions translating to 500 error responses.\n\n if (this.emitter.listenerCount('unhandledException') > 0) {\n await emitAsync(this.emitter, 'unhandledException', {\n error: resolverResult.error,\n request,\n requestId,\n controller: {\n respondWith,\n errorWith,\n },\n })\n\n if (responsePromise.state !== 'pending') {\n return responsePromise\n }\n }\n\n // Unhandled exceptions in the request listeners are\n // synonymous to unhandled exceptions on the server.\n // Those are represented as 500 error responses.\n respondWith(createServerErrorResponse(resolverResult.error))\n return responsePromise\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (isResponseError(mockedResponse)) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This differs from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n errorWith(createNetworkError(mockedResponse))\n } else {\n respondWith(mockedResponse)\n }\n\n return responsePromise\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n this.logger.info('original fetch performed', response)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n }\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IS_PATCHED_MODULE
3
- } from "../../chunk-HAGW22AN.mjs";
3
+ } from "../../chunk-OJ6O4LSC.mjs";
4
4
  import {
5
5
  emitAsync,
6
6
  toInteractiveRequest
@@ -72,21 +72,29 @@ var _FetchInterceptor = class extends Interceptor {
72
72
  { once: true }
73
73
  );
74
74
  }
75
+ const responsePromise = new DeferredPromise();
75
76
  const respondWith = (response) => {
76
- const responseClone = response.clone();
77
- this.emitter.emit("response", {
78
- response: responseClone,
79
- isMockedResponse: true,
80
- request: interactiveRequest,
81
- requestId
82
- });
77
+ this.logger.info("responding with a mock response:", response);
78
+ if (this.emitter.listenerCount("response") > 0) {
79
+ this.logger.info('emitting the "response" event...');
80
+ const responseClone = response.clone();
81
+ this.emitter.emit("response", {
82
+ response: responseClone,
83
+ isMockedResponse: true,
84
+ request: interactiveRequest,
85
+ requestId
86
+ });
87
+ }
83
88
  Object.defineProperty(response, "url", {
84
89
  writable: false,
85
90
  enumerable: true,
86
91
  configurable: false,
87
92
  value: request.url
88
93
  });
89
- return response;
94
+ responsePromise.resolve(response);
95
+ };
96
+ const errorWith = (reason) => {
97
+ responsePromise.reject(reason);
90
98
  };
91
99
  const resolverResult = await until(
92
100
  async () => {
@@ -109,16 +117,41 @@ var _FetchInterceptor = class extends Interceptor {
109
117
  }
110
118
  );
111
119
  if (requestAborted.state === "rejected") {
112
- return Promise.reject(requestAborted.rejectionReason);
120
+ this.logger.info(
121
+ "request has been aborted:",
122
+ requestAborted.rejectionReason
123
+ );
124
+ responsePromise.reject(requestAborted.rejectionReason);
125
+ return responsePromise;
113
126
  }
114
127
  if (resolverResult.error) {
128
+ this.logger.info(
129
+ "request listerner threw an error:",
130
+ resolverResult.error
131
+ );
115
132
  if (resolverResult.error instanceof Response) {
116
133
  if (isResponseError(resolverResult.error)) {
117
- return Promise.reject(createNetworkError(resolverResult.error));
134
+ errorWith(createNetworkError(resolverResult.error));
135
+ } else {
136
+ respondWith(resolverResult.error);
118
137
  }
119
- return respondWith(resolverResult.error);
120
138
  }
121
- return createServerErrorResponse(resolverResult.error);
139
+ if (this.emitter.listenerCount("unhandledException") > 0) {
140
+ await emitAsync(this.emitter, "unhandledException", {
141
+ error: resolverResult.error,
142
+ request,
143
+ requestId,
144
+ controller: {
145
+ respondWith,
146
+ errorWith
147
+ }
148
+ });
149
+ if (responsePromise.state !== "pending") {
150
+ return responsePromise;
151
+ }
152
+ }
153
+ respondWith(createServerErrorResponse(resolverResult.error));
154
+ return responsePromise;
122
155
  }
123
156
  const mockedResponse = resolverResult.data;
124
157
  if (mockedResponse && !((_a = request.signal) == null ? void 0 : _a.aborted)) {
@@ -127,20 +160,25 @@ var _FetchInterceptor = class extends Interceptor {
127
160
  this.logger.info(
128
161
  "received a network error response, rejecting the request promise..."
129
162
  );
130
- return Promise.reject(createNetworkError(mockedResponse));
163
+ errorWith(createNetworkError(mockedResponse));
164
+ } else {
165
+ respondWith(mockedResponse);
131
166
  }
132
- return respondWith(mockedResponse);
167
+ return responsePromise;
133
168
  }
134
169
  this.logger.info("no mocked response received!");
135
170
  return pureFetch(request).then((response) => {
136
- const responseClone = response.clone();
137
- this.logger.info("original fetch performed", responseClone);
138
- this.emitter.emit("response", {
139
- response: responseClone,
140
- isMockedResponse: false,
141
- request: interactiveRequest,
142
- requestId
143
- });
171
+ this.logger.info("original fetch performed", response);
172
+ if (this.emitter.listenerCount("response") > 0) {
173
+ this.logger.info('emitting the "response" event...');
174
+ const responseClone = response.clone();
175
+ this.emitter.emit("response", {
176
+ response: responseClone,
177
+ isMockedResponse: false,
178
+ request: interactiveRequest,
179
+ requestId
180
+ });
181
+ }
144
182
  return response;
145
183
  });
146
184
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport {\n createServerErrorResponse,\n isResponseError,\n} from '../../utils/responseUtils'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const respondWith = (response: Response): Response => {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return response\n }\n\n const resolverResult = await until<unknown, Response | undefined>(\n async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n }\n )\n\n if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n // Treat thrown Responses as mocked responses.\n if (resolverResult.error instanceof Response) {\n // Treat thrown Response.error() as a request error.\n if (isResponseError(resolverResult.error)) {\n return Promise.reject(createNetworkError(resolverResult.error))\n }\n\n // Treat the rest of thrown Responses as mocked responses.\n return respondWith(resolverResult.error)\n }\n\n // Unhandled exceptions in the request listeners are\n // synonymous to unhandled exceptions on the server.\n // Those are represented as 500 error responses.\n return createServerErrorResponse(resolverResult.error)\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (isResponseError(mockedResponse)) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This differs from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n return Promise.reject(createNetworkError(mockedResponse))\n }\n\n return respondWith(mockedResponse)\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n const responseClone = response.clone()\n this.logger.info('original fetch performed', responseClone)\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n"],"mappings":"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","names":["mockedResponse"]}
1
+ {"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport {\n createServerErrorResponse,\n isResponseError,\n} from '../../utils/responseUtils'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const respondWith = (response: Response): void => {\n this.logger.info('responding with a mock response:', response)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n }\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n responsePromise.resolve(response)\n }\n\n const errorWith = (reason: unknown): void => {\n responsePromise.reject(reason)\n }\n\n const resolverResult = await until<unknown, Response | undefined>(\n async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n }\n )\n\n if (requestAborted.state === 'rejected') {\n this.logger.info(\n 'request has been aborted:',\n requestAborted.rejectionReason\n )\n\n responsePromise.reject(requestAborted.rejectionReason)\n return responsePromise\n }\n\n if (resolverResult.error) {\n this.logger.info(\n 'request listerner threw an error:',\n resolverResult.error\n )\n\n // Treat thrown Responses as mocked responses.\n if (resolverResult.error instanceof Response) {\n // Treat thrown Response.error() as a request error.\n if (isResponseError(resolverResult.error)) {\n errorWith(createNetworkError(resolverResult.error))\n } else {\n // Treat the rest of thrown Responses as mocked responses.\n respondWith(resolverResult.error)\n }\n }\n\n // Emit the \"unhandledException\" interceptor event so the client\n // can opt-out from exceptions translating to 500 error responses.\n\n if (this.emitter.listenerCount('unhandledException') > 0) {\n await emitAsync(this.emitter, 'unhandledException', {\n error: resolverResult.error,\n request,\n requestId,\n controller: {\n respondWith,\n errorWith,\n },\n })\n\n if (responsePromise.state !== 'pending') {\n return responsePromise\n }\n }\n\n // Unhandled exceptions in the request listeners are\n // synonymous to unhandled exceptions on the server.\n // Those are represented as 500 error responses.\n respondWith(createServerErrorResponse(resolverResult.error))\n return responsePromise\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (isResponseError(mockedResponse)) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This differs from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n errorWith(createNetworkError(mockedResponse))\n } else {\n respondWith(mockedResponse)\n }\n\n return responsePromise\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n this.logger.info('original fetch performed', response)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n }\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n"],"mappings":"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","names":["mockedResponse"]}
@@ -3,7 +3,7 @@ import { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest/index.
3
3
  import 'http';
4
4
  import 'https';
5
5
  import 'strict-event-emitter';
6
- import '../Interceptor-6696a18d.js';
6
+ import '../Interceptor-88ee47c0.js';
7
7
  import '@open-draft/deferred-promise';
8
8
  import '@open-draft/logger';
9
9
 
@@ -1,18 +1,18 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkUUKG6TUDjs = require('../chunk-UUKG6TUD.js');
3
+ var _chunkHAIWBQD5js = require('../chunk-HAIWBQD5.js');
4
4
 
5
5
 
6
- var _chunkKHEPLFR3js = require('../chunk-KHEPLFR3.js');
6
+ var _chunkAPT7KA3Bjs = require('../chunk-APT7KA3B.js');
7
7
  require('../chunk-LK6DILFK.js');
8
- require('../chunk-FZJKKO5H.js');
8
+ require('../chunk-EIBTX65O.js');
9
9
  require('../chunk-MQJ3JOOK.js');
10
10
  require('../chunk-E4AC7YAC.js');
11
11
 
12
12
  // src/presets/node.ts
13
13
  var node_default = [
14
- new (0, _chunkUUKG6TUDjs.ClientRequestInterceptor)(),
15
- new (0, _chunkKHEPLFR3js.XMLHttpRequestInterceptor)()
14
+ new (0, _chunkHAIWBQD5js.ClientRequestInterceptor)(),
15
+ new (0, _chunkAPT7KA3Bjs.XMLHttpRequestInterceptor)()
16
16
  ];
17
17
 
18
18
 
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../chunk-TCATD46T.mjs";
3
+ } from "../chunk-JMNEFEYU.mjs";
4
4
  import {
5
5
  XMLHttpRequestInterceptor
6
- } from "../chunk-OXXEWTTM.mjs";
6
+ } from "../chunk-KSHIDGUL.mjs";
7
7
  import "../chunk-6HYIRFX2.mjs";
8
- import "../chunk-HAGW22AN.mjs";
8
+ import "../chunk-OJ6O4LSC.mjs";
9
9
  import "../chunk-OUWBQF3Z.mjs";
10
10
  import "../chunk-Q7POAM5N.mjs";
11
11
 
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.28.4",
4
+ "version": "0.29.0",
5
5
  "main": "./lib/node/index.js",
6
6
  "module": "./lib/node/index.mjs",
7
7
  "types": "./lib/node/index.d.ts",
package/src/glossary.ts CHANGED
@@ -19,4 +19,15 @@ export type HttpRequestEventMap = {
19
19
  requestId: string
20
20
  }
21
21
  ]
22
+ unhandledException: [
23
+ args: {
24
+ error: unknown
25
+ request: Request
26
+ requestId: string
27
+ controller: {
28
+ respondWith(response: Response): void
29
+ errorWith(error?: Error): void
30
+ }
31
+ }
32
+ ]
22
33
  }
@@ -293,10 +293,34 @@ export class NodeClientRequest extends ClientRequest {
293
293
  return this
294
294
  }
295
295
 
296
- // Unhandled exceptions in the request listeners are
297
- // synonymous to unhandled exceptions on the server.
298
- // Those are represented as 500 error responses.
299
- this.respondWith(createServerErrorResponse(resolverResult.error))
296
+ until(async () => {
297
+ if (this.emitter.listenerCount('unhandledException') > 0) {
298
+ // Emit the "unhandledException" event to allow the client
299
+ // to opt-out from the default handling of exceptions
300
+ // as 500 error responses.
301
+ await emitAsync(this.emitter, 'unhandledException', {
302
+ error: resolverResult.error,
303
+ request: capturedRequest,
304
+ requestId,
305
+ controller: {
306
+ respondWith: this.respondWith.bind(this),
307
+ errorWith: this.errorWith.bind(this),
308
+ },
309
+ })
310
+
311
+ // If after the "unhandledException" listeners are done,
312
+ // the request is either not writable (was mocked) or
313
+ // destroyed (has errored), do nothing.
314
+ if (this.writableEnded || this.destroyed) {
315
+ return
316
+ }
317
+ }
318
+
319
+ // Unhandled exceptions in the request listeners are
320
+ // synonymous to unhandled exceptions on the server.
321
+ // Those are represented as 500 error responses.
322
+ this.respondWith(createServerErrorResponse(resolverResult.error))
323
+ })
300
324
 
301
325
  return this
302
326
  }
@@ -49,10 +49,7 @@ export class XMLHttpRequestController {
49
49
  private responseBuffer: Uint8Array
50
50
  private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>
51
51
 
52
- constructor(
53
- readonly initialRequest: XMLHttpRequest,
54
- public logger: Logger
55
- ) {
52
+ constructor(readonly initialRequest: XMLHttpRequest, public logger: Logger) {
56
53
  this.events = new Map()
57
54
  this.requestId = createRequestId()
58
55
  this.requestHeaders = new Headers()
@@ -103,7 +100,7 @@ export class XMLHttpRequestController {
103
100
  case 'addEventListener': {
104
101
  const [eventName, listener] = args as [
105
102
  keyof XMLHttpRequestEventTargetEventMap,
106
- Function,
103
+ Function
107
104
  ]
108
105
 
109
106
  this.registerEvent(eventName, listener)
@@ -123,7 +120,7 @@ export class XMLHttpRequestController {
123
120
 
124
121
  case 'send': {
125
122
  const [body] = args as [
126
- body?: XMLHttpRequestBodyInit | Document | null,
123
+ body?: XMLHttpRequestBodyInit | Document | null
127
124
  ]
128
125
 
129
126
  if (body != null) {
@@ -524,7 +521,7 @@ export class XMLHttpRequestController {
524
521
  private trigger<
525
522
  EventName extends keyof (XMLHttpRequestEventTargetEventMap & {
526
523
  readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
527
- }),
524
+ })
528
525
  >(eventName: EventName, options?: ProgressEventInit): void {
529
526
  const callback = this.request[`on${eventName}`]
530
527
  const event = createEvent(this.request, eventName, options)
@@ -108,6 +108,31 @@ export function createXMLHttpRequestProxy({
108
108
 
109
109
  return
110
110
  }
111
+
112
+ if (emitter.listenerCount('unhandledException') > 0) {
113
+ // Emit the "unhandledException" event so the client can opt-out
114
+ // from the default exception handling (producing 500 error responses).
115
+ await emitAsync(emitter, 'unhandledException', {
116
+ error: resolverResult.error,
117
+ request,
118
+ requestId,
119
+ controller: {
120
+ respondWith:
121
+ xhrRequestController.respondWith.bind(xhrRequestController),
122
+ errorWith:
123
+ xhrRequestController.errorWith.bind(xhrRequestController),
124
+ },
125
+ })
126
+
127
+ // If any of the "unhandledException" listeners handled the request,
128
+ // do nothing. Note that mocked responses will dispatch
129
+ // HEADERS_RECEIVED (2), then LOADING (3), and DONE (4) can take
130
+ // time as the mocked response body finishes streaming.
131
+ if (originalRequest.readyState > XMLHttpRequest.OPENED) {
132
+ return
133
+ }
134
+ }
135
+
111
136
  // Unhandled exceptions in the request listeners are
112
137
  // synonymous to unhandled exceptions on the server.
113
138
  // Those are represented as 500 error responses.