@mswjs/interceptors 0.26.10 → 0.26.12

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 (123) hide show
  1. package/README.md +74 -74
  2. package/lib/browser/{Interceptor-b7c08a9f.d.ts → Interceptor-af98b768.d.ts} +9 -1
  3. package/lib/{node/chunk-EM6NYHQV.js → browser/chunk-3PGRU2BR.js} +4 -2
  4. package/lib/browser/chunk-3PGRU2BR.js.map +1 -0
  5. package/lib/browser/{chunk-65OQZE6W.mjs → chunk-5E3BR6QC.mjs} +4 -7
  6. package/lib/browser/chunk-5E3BR6QC.mjs.map +1 -0
  7. package/lib/browser/{chunk-P2GC6ZRG.js → chunk-5HSEVANC.js} +8 -7
  8. package/lib/browser/chunk-5HSEVANC.js.map +1 -0
  9. package/lib/{node/chunk-SGO3INLV.mjs → browser/chunk-KHZ3VYHS.mjs} +3 -1
  10. package/lib/browser/chunk-KHZ3VYHS.mjs.map +1 -0
  11. package/lib/browser/{chunk-AMRVY5JV.js → chunk-N6P4WNMD.js} +5 -8
  12. package/lib/browser/chunk-N6P4WNMD.js.map +1 -0
  13. package/lib/browser/{chunk-6ZIL5FW4.mjs → chunk-WVYFUFZR.mjs} +8 -7
  14. package/lib/browser/chunk-WVYFUFZR.mjs.map +1 -0
  15. package/lib/browser/index.d.ts +2 -2
  16. package/lib/browser/index.js +5 -3
  17. package/lib/browser/index.js.map +1 -1
  18. package/lib/browser/index.mjs +3 -1
  19. package/lib/browser/index.mjs.map +1 -1
  20. package/lib/browser/interceptors/WebSocket/index.d.ts +9 -2
  21. package/lib/browser/interceptors/WebSocket/index.js +164 -132
  22. package/lib/browser/interceptors/WebSocket/index.js.map +1 -1
  23. package/lib/browser/interceptors/WebSocket/index.mjs +167 -135
  24. package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -1
  25. package/lib/browser/interceptors/XMLHttpRequest/index.d.ts +1 -1
  26. package/lib/browser/interceptors/XMLHttpRequest/index.js +3 -4
  27. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +2 -3
  28. package/lib/browser/interceptors/fetch/index.d.ts +2 -2
  29. package/lib/browser/interceptors/fetch/index.js +3 -4
  30. package/lib/browser/interceptors/fetch/index.mjs +2 -3
  31. package/lib/browser/presets/browser.d.ts +1 -1
  32. package/lib/browser/presets/browser.js +5 -6
  33. package/lib/browser/presets/browser.js.map +1 -1
  34. package/lib/browser/presets/browser.mjs +3 -4
  35. package/lib/browser/presets/browser.mjs.map +1 -1
  36. package/lib/node/{BatchInterceptor-9785c567.d.ts → BatchInterceptor-cb145daa.d.ts} +1 -1
  37. package/lib/node/{Interceptor-7a701c1f.d.ts → Interceptor-6696a18d.d.ts} +9 -1
  38. package/lib/node/RemoteHttpInterceptor.d.ts +2 -2
  39. package/lib/node/RemoteHttpInterceptor.js +15 -15
  40. package/lib/node/RemoteHttpInterceptor.mjs +9 -9
  41. package/lib/node/{chunk-7UF4SATD.mjs → chunk-5JPIH6H2.mjs} +15 -11
  42. package/lib/node/chunk-5JPIH6H2.mjs.map +1 -0
  43. package/lib/node/{chunk-6ZZBHB3P.mjs → chunk-66HWDCQ2.mjs} +2 -2
  44. package/lib/node/{chunk-5IBJT662.mjs → chunk-7GOGV57P.mjs} +1 -11
  45. package/lib/node/chunk-7GOGV57P.mjs.map +1 -0
  46. package/lib/node/{chunk-4NTPASNT.js → chunk-AQD7SRYT.js} +2 -12
  47. package/lib/node/chunk-AQD7SRYT.js.map +1 -0
  48. package/lib/node/{chunk-DERTLGL3.mjs → chunk-CDO7LUCB.mjs} +1 -1
  49. package/lib/node/{chunk-LK6DILFK.js → chunk-DWNXSX4R.js} +1 -1
  50. package/lib/node/{chunk-FZJKKO5H.js → chunk-HOHLBCZO.js} +1 -1
  51. package/lib/node/{chunk-IBYBTTYK.mjs → chunk-LVMKW3BV.mjs} +1 -1
  52. package/lib/node/{chunk-YXLNUCS2.mjs → chunk-OIJM4NGH.mjs} +12 -11
  53. package/lib/node/chunk-OIJM4NGH.mjs.map +1 -0
  54. package/lib/node/{chunk-Y6GRL6UD.js → chunk-OQFUVENL.js} +1 -1
  55. package/lib/node/{chunk-6HYIRFX2.mjs → chunk-ORI3LFXR.mjs} +1 -1
  56. package/lib/{browser/chunk-EM6NYHQV.js → node/chunk-Q2ZAXW2V.js} +9 -3
  57. package/lib/node/chunk-Q2ZAXW2V.js.map +1 -0
  58. package/lib/{browser/chunk-SGO3INLV.mjs → node/chunk-TNNJTZLG.mjs} +7 -1
  59. package/lib/node/chunk-TNNJTZLG.mjs.map +1 -0
  60. package/lib/node/{chunk-NWYLCOX6.js → chunk-WJBJLZSK.js} +22 -18
  61. package/lib/node/chunk-WJBJLZSK.js.map +1 -0
  62. package/lib/node/{chunk-JSSEHRRB.js → chunk-XRANZXDY.js} +1 -1
  63. package/lib/node/{chunk-HAGW22AN.mjs → chunk-YQUDKP67.mjs} +1 -1
  64. package/lib/node/{chunk-ZW4SKXEP.js → chunk-Z2GCDAWN.js} +3 -3
  65. package/lib/node/{chunk-UJRRZPRV.js → chunk-ZOS6ZFFL.js} +14 -13
  66. package/lib/node/chunk-ZOS6ZFFL.js.map +1 -0
  67. package/lib/node/index.d.ts +2 -2
  68. package/lib/node/index.js +8 -6
  69. package/lib/node/index.js.map +1 -1
  70. package/lib/node/index.mjs +7 -5
  71. package/lib/node/index.mjs.map +1 -1
  72. package/lib/node/interceptors/ClientRequest/index.d.ts +1 -1
  73. package/lib/node/interceptors/ClientRequest/index.js +6 -6
  74. package/lib/node/interceptors/ClientRequest/index.mjs +5 -5
  75. package/lib/node/interceptors/XMLHttpRequest/index.d.ts +1 -1
  76. package/lib/node/interceptors/XMLHttpRequest/index.js +7 -7
  77. package/lib/node/interceptors/XMLHttpRequest/index.mjs +6 -6
  78. package/lib/node/interceptors/fetch/index.d.ts +2 -2
  79. package/lib/node/interceptors/fetch/index.js +13 -13
  80. package/lib/node/interceptors/fetch/index.js.map +1 -1
  81. package/lib/node/interceptors/fetch/index.mjs +9 -9
  82. package/lib/node/interceptors/fetch/index.mjs.map +1 -1
  83. package/lib/node/presets/node.d.ts +1 -1
  84. package/lib/node/presets/node.js +10 -10
  85. package/lib/node/presets/node.mjs +8 -8
  86. package/package.json +2 -2
  87. package/src/Interceptor.ts +11 -1
  88. package/src/crypto-shim.ts +3 -0
  89. package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +0 -5
  90. package/src/interceptors/ClientRequest/NodeClientRequest.ts +5 -5
  91. package/src/interceptors/WebSocket/WebSocketClientConnection.ts +1 -2
  92. package/src/interceptors/WebSocket/WebSocketServerConnection.ts +87 -21
  93. package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +13 -7
  94. package/src/interceptors/fetch/index.ts +2 -3
  95. package/lib/browser/chunk-65OQZE6W.mjs.map +0 -1
  96. package/lib/browser/chunk-6ZIL5FW4.mjs.map +0 -1
  97. package/lib/browser/chunk-AMRVY5JV.js.map +0 -1
  98. package/lib/browser/chunk-CEKC26XE.mjs +0 -13
  99. package/lib/browser/chunk-CEKC26XE.mjs.map +0 -1
  100. package/lib/browser/chunk-EM6NYHQV.js.map +0 -1
  101. package/lib/browser/chunk-LQSFVQBO.js +0 -13
  102. package/lib/browser/chunk-LQSFVQBO.js.map +0 -1
  103. package/lib/browser/chunk-P2GC6ZRG.js.map +0 -1
  104. package/lib/browser/chunk-SGO3INLV.mjs.map +0 -1
  105. package/lib/node/chunk-4NTPASNT.js.map +0 -1
  106. package/lib/node/chunk-5IBJT662.mjs.map +0 -1
  107. package/lib/node/chunk-7UF4SATD.mjs.map +0 -1
  108. package/lib/node/chunk-EM6NYHQV.js.map +0 -1
  109. package/lib/node/chunk-NWYLCOX6.js.map +0 -1
  110. package/lib/node/chunk-SGO3INLV.mjs.map +0 -1
  111. package/lib/node/chunk-UJRRZPRV.js.map +0 -1
  112. package/lib/node/chunk-YXLNUCS2.mjs.map +0 -1
  113. package/src/utils/uuid.ts +0 -7
  114. /package/lib/node/{chunk-6ZZBHB3P.mjs.map → chunk-66HWDCQ2.mjs.map} +0 -0
  115. /package/lib/node/{chunk-DERTLGL3.mjs.map → chunk-CDO7LUCB.mjs.map} +0 -0
  116. /package/lib/node/{chunk-LK6DILFK.js.map → chunk-DWNXSX4R.js.map} +0 -0
  117. /package/lib/node/{chunk-FZJKKO5H.js.map → chunk-HOHLBCZO.js.map} +0 -0
  118. /package/lib/node/{chunk-IBYBTTYK.mjs.map → chunk-LVMKW3BV.mjs.map} +0 -0
  119. /package/lib/node/{chunk-Y6GRL6UD.js.map → chunk-OQFUVENL.js.map} +0 -0
  120. /package/lib/node/{chunk-6HYIRFX2.mjs.map → chunk-ORI3LFXR.mjs.map} +0 -0
  121. /package/lib/node/{chunk-JSSEHRRB.js.map → chunk-XRANZXDY.js.map} +0 -0
  122. /package/lib/node/{chunk-HAGW22AN.mjs.map → chunk-YQUDKP67.mjs.map} +0 -0
  123. /package/lib/node/{chunk-ZW4SKXEP.js.map → chunk-Z2GCDAWN.js.map} +0 -0
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  isPropertyAccessible
3
- } from "../../chunk-DERTLGL3.mjs";
3
+ } from "../../chunk-CDO7LUCB.mjs";
4
4
  import {
5
5
  IS_PATCHED_MODULE
6
- } from "../../chunk-HAGW22AN.mjs";
6
+ } from "../../chunk-YQUDKP67.mjs";
7
7
  import {
8
8
  emitAsync,
9
- toInteractiveRequest,
10
- uuidv4
11
- } from "../../chunk-5IBJT662.mjs";
9
+ toInteractiveRequest
10
+ } from "../../chunk-7GOGV57P.mjs";
12
11
  import {
13
- Interceptor
14
- } from "../../chunk-SGO3INLV.mjs";
12
+ Interceptor,
13
+ crypto
14
+ } from "../../chunk-TNNJTZLG.mjs";
15
15
 
16
16
  // src/interceptors/fetch/index.ts
17
17
  import { invariant } from "outvariant";
@@ -36,7 +36,7 @@ var _FetchInterceptor = class extends Interceptor {
36
36
  checkEnvironment() {
37
37
  return typeof globalThis !== "undefined" && typeof globalThis.fetch !== "undefined";
38
38
  }
39
- setup() {
39
+ async setup() {
40
40
  const pureFetch = globalThis.fetch;
41
41
  invariant(
42
42
  !pureFetch[IS_PATCHED_MODULE],
@@ -44,7 +44,7 @@ var _FetchInterceptor = class extends Interceptor {
44
44
  );
45
45
  globalThis.fetch = async (input, init) => {
46
46
  var _a;
47
- const requestId = uuidv4();
47
+ const requestId = crypto.randomUUID();
48
48
  const resolvedInput = typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.origin) : input;
49
49
  const request = new Request(resolvedInput, init);
50
50
  this.logger.info("[%s] %s", request.method, request.url);
@@ -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 { uuidv4 } from '../../utils/uuid'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { isPropertyAccessible } from '../../utils/isPropertyAccessible'\nimport { canParseUrl } from '../../utils/canParseUrl'\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 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 = uuidv4()\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 resolverResult = await until(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 if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n return Promise.reject(createNetworkError(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 (\n isPropertyAccessible(mockedResponse, 'type') &&\n mockedResponse.type === 'error'\n ) {\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 different 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 // 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 = mockedResponse.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(mockedResponse, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return 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 { isPropertyAccessible } from '../../utils/isPropertyAccessible'\nimport { canParseUrl } from '../../utils/canParseUrl'\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 = crypto.randomUUID()\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 resolverResult = await until(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 if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n return Promise.reject(createNetworkError(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 (\n isPropertyAccessible(mockedResponse, 'type') &&\n mockedResponse.type === 'error'\n ) {\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 different 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 // 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 = mockedResponse.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(mockedResponse, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return 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"]}
@@ -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-7a701c1f.js';
6
+ import '../Interceptor-6696a18d.js';
7
7
  import '@open-draft/deferred-promise';
8
8
  import '@open-draft/logger';
9
9
 
@@ -1,20 +1,20 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkUJRRZPRVjs = require('../chunk-UJRRZPRV.js');
3
+ var _chunkZOS6ZFFLjs = require('../chunk-ZOS6ZFFL.js');
4
4
 
5
5
 
6
- var _chunkNWYLCOX6js = require('../chunk-NWYLCOX6.js');
7
- require('../chunk-LK6DILFK.js');
8
- require('../chunk-JSSEHRRB.js');
9
- require('../chunk-Y6GRL6UD.js');
10
- require('../chunk-FZJKKO5H.js');
11
- require('../chunk-4NTPASNT.js');
12
- require('../chunk-EM6NYHQV.js');
6
+ var _chunkWJBJLZSKjs = require('../chunk-WJBJLZSK.js');
7
+ require('../chunk-DWNXSX4R.js');
8
+ require('../chunk-XRANZXDY.js');
9
+ require('../chunk-OQFUVENL.js');
10
+ require('../chunk-HOHLBCZO.js');
11
+ require('../chunk-AQD7SRYT.js');
12
+ require('../chunk-Q2ZAXW2V.js');
13
13
 
14
14
  // src/presets/node.ts
15
15
  var node_default = [
16
- new (0, _chunkUJRRZPRVjs.ClientRequestInterceptor)(),
17
- new (0, _chunkNWYLCOX6js.XMLHttpRequestInterceptor)()
16
+ new (0, _chunkZOS6ZFFLjs.ClientRequestInterceptor)(),
17
+ new (0, _chunkWJBJLZSKjs.XMLHttpRequestInterceptor)()
18
18
  ];
19
19
 
20
20
 
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  ClientRequestInterceptor
3
- } from "../chunk-YXLNUCS2.mjs";
3
+ } from "../chunk-OIJM4NGH.mjs";
4
4
  import {
5
5
  XMLHttpRequestInterceptor
6
- } from "../chunk-7UF4SATD.mjs";
7
- import "../chunk-6HYIRFX2.mjs";
8
- import "../chunk-IBYBTTYK.mjs";
9
- import "../chunk-DERTLGL3.mjs";
10
- import "../chunk-HAGW22AN.mjs";
11
- import "../chunk-5IBJT662.mjs";
12
- import "../chunk-SGO3INLV.mjs";
6
+ } from "../chunk-5JPIH6H2.mjs";
7
+ import "../chunk-ORI3LFXR.mjs";
8
+ import "../chunk-LVMKW3BV.mjs";
9
+ import "../chunk-CDO7LUCB.mjs";
10
+ import "../chunk-YQUDKP67.mjs";
11
+ import "../chunk-7GOGV57P.mjs";
12
+ import "../chunk-TNNJTZLG.mjs";
13
13
 
14
14
  // src/presets/node.ts
15
15
  var node_default = [
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.26.10",
4
+ "version": "0.26.12",
5
5
  "main": "./lib/node/index.js",
6
6
  "module": "./lib/node/index.mjs",
7
7
  "types": "./lib/node/index.d.ts",
@@ -101,7 +101,7 @@
101
101
  "@commitlint/cli": "^16.0.2",
102
102
  "@commitlint/config-conventional": "^16.0.0",
103
103
  "@open-draft/test-server": "^0.5.1",
104
- "@ossjs/release": "^0.7.2",
104
+ "@ossjs/release": "^0.8.1",
105
105
  "@playwright/test": "^1.37.1",
106
106
  "@types/cors": "^2.8.12",
107
107
  "@types/express": "^4.17.13",
@@ -1,9 +1,19 @@
1
1
  import { Logger } from '@open-draft/logger'
2
- import { Emitter, EventMap, Listener } from 'strict-event-emitter'
2
+ import { Emitter, Listener } from 'strict-event-emitter'
3
3
 
4
4
  export type InterceptorEventMap = Record<string, any>
5
5
  export type InterceptorSubscription = () => void
6
6
 
7
+ /**
8
+ * Request header name to detect when a single request
9
+ * is being handled by nested interceptors (XHR -> ClientRequest).
10
+ * Obscure by design to prevent collisions with user-defined headers.
11
+ * Ideally, come up with the Interceptor-level mechanism for this.
12
+ * @see https://github.com/mswjs/interceptors/issues/378
13
+ */
14
+ export const INTERNAL_REQUEST_ID_HEADER_NAME =
15
+ 'x-interceptors-internal-request-id'
16
+
7
17
  export function getGlobalSymbol<V>(symbol: Symbol): V | undefined {
8
18
  return (
9
19
  // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587
@@ -0,0 +1,3 @@
1
+ import crypto from 'node:crypto'
2
+
3
+ export { crypto }
@@ -11,11 +11,6 @@ import { normalizeClientRequestArgs } from './utils/normalizeClientRequestArgs'
11
11
  import { sleep } from '../../../test/helpers'
12
12
  import { HttpRequestEventMap } from '../../glossary'
13
13
 
14
- interface ErrorConnectionRefused extends NodeJS.ErrnoException {
15
- address: string
16
- port: number
17
- }
18
-
19
14
  const httpServer = new HttpServer((app) => {
20
15
  app.post('/comment', (_req, res) => {
21
16
  res.status(200).send('original-response')
@@ -1,4 +1,4 @@
1
- import { ClientRequest, IncomingMessage } from 'http'
1
+ import { ClientRequest, IncomingMessage } from 'node:http'
2
2
  import type { Logger } from '@open-draft/logger'
3
3
  import { until } from '@open-draft/until'
4
4
  import { DeferredPromise } from '@open-draft/deferred-promise'
@@ -17,10 +17,10 @@ import { cloneIncomingMessage } from './utils/cloneIncomingMessage'
17
17
  import { createResponse } from './utils/createResponse'
18
18
  import { createRequest } from './utils/createRequest'
19
19
  import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
20
- import { uuidv4 } from '../../utils/uuid'
21
20
  import { emitAsync } from '../../utils/emitAsync'
22
21
  import { getRawFetchHeaders } from '../../utils/getRawFetchHeaders'
23
22
  import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
23
+ import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
24
24
 
25
25
  export type Protocol = 'http' | 'https'
26
26
 
@@ -151,7 +151,7 @@ export class NodeClientRequest extends ClientRequest {
151
151
  end(...args: any): this {
152
152
  this.logger.info('end', args)
153
153
 
154
- const requestId = uuidv4()
154
+ const requestId = crypto.randomUUID()
155
155
 
156
156
  const [chunk, encoding, callback] = normalizeClientRequestEndArgs(...args)
157
157
  this.logger.info('normalized arguments:', { chunk, encoding, callback })
@@ -189,8 +189,8 @@ export class NodeClientRequest extends ClientRequest {
189
189
  // in another (parent) interceptor (like XMLHttpRequest -> ClientRequest).
190
190
  // That means some interceptor up the chain has concluded that
191
191
  // this request must be performed as-is.
192
- if (this.getHeader('X-Request-Id') != null) {
193
- this.removeHeader('X-Request-Id')
192
+ if (this.hasHeader(INTERNAL_REQUEST_ID_HEADER_NAME)) {
193
+ this.removeHeader(INTERNAL_REQUEST_ID_HEADER_NAME)
194
194
  return this.passthrough(chunk, encoding, callback)
195
195
  }
196
196
 
@@ -9,7 +9,6 @@ import type { WebSocketData, WebSocketTransport } from './WebSocketTransport'
9
9
  import { WebSocketMessageListener } from './WebSocketOverride'
10
10
  import { bindEvent } from './utils/bindEvent'
11
11
  import { CloseEvent } from './utils/events'
12
- import { uuidv4 } from '../../utils/uuid'
13
12
 
14
13
  const kEmitter = Symbol('kEmitter')
15
14
 
@@ -37,7 +36,7 @@ export class WebSocketClientConnection
37
36
  public readonly socket: WebSocket,
38
37
  private readonly transport: WebSocketTransport
39
38
  ) {
40
- this.id = uuidv4()
39
+ this.id = crypto.randomUUID()
41
40
  this.url = new URL(socket.url)
42
41
  this[kEmitter] = new EventTarget()
43
42
 
@@ -1,9 +1,9 @@
1
1
  import { invariant } from 'outvariant'
2
- import type { WebSocketOverride } from './WebSocketOverride'
2
+ import { kClose, WebSocketOverride } from './WebSocketOverride'
3
3
  import type { WebSocketData } from './WebSocketTransport'
4
4
  import type { WebSocketClassTransport } from './WebSocketClassTransport'
5
5
  import { bindEvent } from './utils/bindEvent'
6
- import { CancelableMessageEvent } from './utils/events'
6
+ import { CancelableMessageEvent, CloseEvent } from './utils/events'
7
7
 
8
8
  const kEmitter = Symbol('kEmitter')
9
9
 
@@ -17,6 +17,7 @@ export class WebSocketServerConnection {
17
17
  * A WebSocket instance connected to the original server.
18
18
  */
19
19
  private realWebSocket?: WebSocket
20
+ private mockCloseController: AbortController
20
21
  private [kEmitter]: EventTarget
21
22
 
22
23
  constructor(
@@ -25,6 +26,7 @@ export class WebSocketServerConnection {
25
26
  private readonly createConnection: () => WebSocket
26
27
  ) {
27
28
  this[kEmitter] = new EventTarget()
29
+ this.mockCloseController = new AbortController()
28
30
 
29
31
  // Handle incoming events from the actual server.
30
32
  // The (mock) WebSocket instance will call this
@@ -97,36 +99,36 @@ export class WebSocketServerConnection {
97
99
  */
98
100
  public connect(): void {
99
101
  invariant(
100
- this.readyState === -1,
102
+ !this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN,
101
103
  'Failed to call "connect()" on the original WebSocket instance: the connection already open'
102
104
  )
103
105
 
104
- const ws = this.createConnection()
106
+ const realWebSocket = this.createConnection()
105
107
 
106
108
  // Inherit the binary type from the mock WebSocket client.
107
- ws.binaryType = this.socket.binaryType
108
-
109
- // Close the original connection when the (mock)
110
- // client closes, regardless of the reason.
111
- this.socket.addEventListener(
112
- 'close',
113
- (event) => {
114
- ws.close(event.code, event.reason)
115
- },
116
- { once: true }
117
- )
109
+ realWebSocket.binaryType = this.socket.binaryType
118
110
 
119
- ws.addEventListener('message', (event) => {
111
+ realWebSocket.addEventListener('message', (event) => {
120
112
  this.transport.onIncoming(event)
121
113
  })
122
114
 
115
+ // Close the original connection when the mock client closes.
116
+ // E.g. "client.close()" was called.
117
+ this.socket.addEventListener('close', this.handleMockClose.bind(this), {
118
+ signal: this.mockCloseController.signal,
119
+ })
120
+
121
+ // Forward the "close" event to let the interceptor handle
122
+ // closures initiated by the original server.
123
+ realWebSocket.addEventListener('close', this.handleRealClose.bind(this))
124
+
123
125
  // Forward server errors to the WebSocket client as-is.
124
126
  // We may consider exposing them to the interceptor in the future.
125
- ws.addEventListener('error', () => {
127
+ realWebSocket.addEventListener('error', () => {
126
128
  this.socket.dispatchEvent(bindEvent(this.socket, new Event('error')))
127
129
  })
128
130
 
129
- this.realWebSocket = ws
131
+ this.realWebSocket = realWebSocket
130
132
  }
131
133
 
132
134
  /**
@@ -145,7 +147,7 @@ export class WebSocketServerConnection {
145
147
  }
146
148
 
147
149
  /**
148
- * Removes the listener for the given event.
150
+ * Remove the listener for the given event.
149
151
  */
150
152
  public removeEventListener<K extends keyof WebSocketEventMap>(
151
153
  event: K,
@@ -168,16 +170,25 @@ export class WebSocketServerConnection {
168
170
  */
169
171
  public send(data: WebSocketData): void {
170
172
  const { realWebSocket } = this
173
+
171
174
  invariant(
172
175
  realWebSocket,
173
- 'Failed to call "server.send()" for "%s": the connection is not open. Did you forget to call "await server.connect()"?',
176
+ 'Failed to call "server.send()" for "%s": the connection is not open. Did you forget to call "server.connect()"?',
174
177
  this.socket.url
175
178
  )
176
179
 
180
+ // Silently ignore writes on the closed original WebSocket.
181
+ if (
182
+ realWebSocket.readyState === WebSocket.CLOSING ||
183
+ realWebSocket.readyState === WebSocket.CLOSED
184
+ ) {
185
+ return
186
+ }
187
+
177
188
  // Delegate the send to when the original connection is open.
178
189
  // Unlike the mock, connecting to the original server may take time
179
190
  // so we cannot call this on the next tick.
180
- if (realWebSocket.readyState === realWebSocket.CONNECTING) {
191
+ if (realWebSocket.readyState === WebSocket.CONNECTING) {
181
192
  realWebSocket.addEventListener(
182
193
  'open',
183
194
  () => {
@@ -191,4 +202,59 @@ export class WebSocketServerConnection {
191
202
  // Send the data to the original WebSocket server.
192
203
  realWebSocket.send(data)
193
204
  }
205
+
206
+ /**
207
+ * Close the actual server connection.
208
+ */
209
+ public close(): void {
210
+ const { realWebSocket } = this
211
+
212
+ invariant(
213
+ realWebSocket,
214
+ 'Failed to close server connection for "%s": the connection is not open. Did you forget to call "server.connect()"?',
215
+ this.socket.url
216
+ )
217
+
218
+ realWebSocket.removeEventListener('close', this.handleRealClose)
219
+
220
+ if (
221
+ realWebSocket.readyState === WebSocket.CLOSING ||
222
+ realWebSocket.readyState === WebSocket.CLOSED
223
+ ) {
224
+ return
225
+ }
226
+
227
+ realWebSocket.close()
228
+ }
229
+
230
+ private handleMockClose(_event: Event): void {
231
+ // Close the original connection if the mock client closes.
232
+ if (this.realWebSocket) {
233
+ this.realWebSocket.close()
234
+ }
235
+ }
236
+
237
+ private handleRealClose(event: CloseEvent): void {
238
+ // For closures originating from the original server,
239
+ // remove the "close" listener from the mock client.
240
+ // original close -> (?) client[kClose]() --X-> "close" (again).
241
+ this.mockCloseController.abort()
242
+
243
+ const closeEvent = bindEvent(
244
+ this.realWebSocket,
245
+ new CloseEvent('close', event)
246
+ )
247
+
248
+ this[kEmitter].dispatchEvent(closeEvent)
249
+
250
+ // If the close event from the server hasn't been prevented,
251
+ // forward the closure to the mock client.
252
+ if (!closeEvent.defaultPrevented) {
253
+ // Close the intercepted client forcefully to
254
+ // allow non-configurable status codes from the server.
255
+ // If the socket has been closed by now, no harm calling
256
+ // this again—it will have no effect.
257
+ this.socket[kClose](event.code, event.reason)
258
+ }
259
+ }
194
260
  }
@@ -11,8 +11,8 @@ import {
11
11
  import { createProxy } from '../../utils/createProxy'
12
12
  import { isDomParserSupportedType } from './utils/isDomParserSupportedType'
13
13
  import { parseJson } from '../../utils/parseJson'
14
- import { uuidv4 } from '../../utils/uuid'
15
14
  import { createResponse } from './utils/createResponse'
15
+ import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
16
16
 
17
17
  const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
18
18
  const IS_NODE = isNodeProcess()
@@ -48,9 +48,12 @@ export class XMLHttpRequestController {
48
48
  private responseBuffer: Uint8Array
49
49
  private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>
50
50
 
51
- constructor(readonly initialRequest: XMLHttpRequest, public logger: Logger) {
51
+ constructor(
52
+ readonly initialRequest: XMLHttpRequest,
53
+ public logger: Logger
54
+ ) {
52
55
  this.events = new Map()
53
- this.requestId = uuidv4()
56
+ this.requestId = crypto.randomUUID()
54
57
  this.requestHeaders = new Headers()
55
58
  this.responseBuffer = new Uint8Array()
56
59
 
@@ -99,7 +102,7 @@ export class XMLHttpRequestController {
99
102
  case 'addEventListener': {
100
103
  const [eventName, listener] = args as [
101
104
  keyof XMLHttpRequestEventTargetEventMap,
102
- Function
105
+ Function,
103
106
  ]
104
107
 
105
108
  this.registerEvent(eventName, listener)
@@ -119,7 +122,7 @@ export class XMLHttpRequestController {
119
122
 
120
123
  case 'send': {
121
124
  const [body] = args as [
122
- body?: XMLHttpRequestBodyInit | Document | null
125
+ body?: XMLHttpRequestBodyInit | Document | null,
123
126
  ]
124
127
 
125
128
  if (body != null) {
@@ -180,7 +183,10 @@ export class XMLHttpRequestController {
180
183
  * handle the same request at the same time (e.g. emit the "response" event twice).
181
184
  */
182
185
  if (IS_NODE) {
183
- this.request.setRequestHeader('X-Request-Id', this.requestId!)
186
+ this.request.setRequestHeader(
187
+ INTERNAL_REQUEST_ID_HEADER_NAME,
188
+ this.requestId!
189
+ )
184
190
  }
185
191
 
186
192
  return invoke()
@@ -517,7 +523,7 @@ export class XMLHttpRequestController {
517
523
  private trigger<
518
524
  EventName extends keyof (XMLHttpRequestEventTargetEventMap & {
519
525
  readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
520
- })
526
+ }),
521
527
  >(eventName: EventName, options?: ProgressEventInit): void {
522
528
  const callback = this.request[`on${eventName}`]
523
529
  const event = createEvent(this.request, eventName, options)
@@ -3,7 +3,6 @@ import { DeferredPromise } from '@open-draft/deferred-promise'
3
3
  import { until } from '@open-draft/until'
4
4
  import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
5
5
  import { Interceptor } from '../../Interceptor'
6
- import { uuidv4 } from '../../utils/uuid'
7
6
  import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
8
7
  import { emitAsync } from '../../utils/emitAsync'
9
8
  import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
@@ -23,7 +22,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
23
22
  )
24
23
  }
25
24
 
26
- protected setup() {
25
+ protected async setup() {
27
26
  const pureFetch = globalThis.fetch
28
27
 
29
28
  invariant(
@@ -32,7 +31,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
32
31
  )
33
32
 
34
33
  globalThis.fetch = async (input, init) => {
35
- const requestId = uuidv4()
34
+ const requestId = crypto.randomUUID()
36
35
 
37
36
  /**
38
37
  * @note Resolve potentially relative request URL
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/interceptors/fetch/index.ts","../../src/utils/isPropertyAccessible.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 { uuidv4 } from '../../utils/uuid'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { isPropertyAccessible } from '../../utils/isPropertyAccessible'\nimport { canParseUrl } from '../../utils/canParseUrl'\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 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 = uuidv4()\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 resolverResult = await until(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 if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n return Promise.reject(createNetworkError(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 (\n isPropertyAccessible(mockedResponse, 'type') &&\n mockedResponse.type === 'error'\n ) {\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 different 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 // 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 = mockedResponse.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(mockedResponse, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return 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 * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\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"]}