@mswjs/interceptors 0.39.8 → 0.41.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 (282) hide show
  1. package/ClientRequest/package.json +7 -2
  2. package/RemoteHttpInterceptor/package.json +7 -2
  3. package/WebSocket/package.json +9 -2
  4. package/XMLHttpRequest/package.json +9 -3
  5. package/fetch/package.json +9 -3
  6. package/lib/browser/Interceptor-2mUoKZL1.d.mts +65 -0
  7. package/lib/browser/Interceptor-Deczogc8.d.cts +65 -0
  8. package/lib/browser/XMLHttpRequest-BUfglQD1.cjs +761 -0
  9. package/lib/browser/XMLHttpRequest-BUfglQD1.cjs.map +1 -0
  10. package/lib/browser/XMLHttpRequest-DS5fc8Qs.mjs +756 -0
  11. package/lib/browser/XMLHttpRequest-DS5fc8Qs.mjs.map +1 -0
  12. package/lib/browser/bufferUtils-BiiO6HZv.mjs +20 -0
  13. package/lib/browser/bufferUtils-BiiO6HZv.mjs.map +1 -0
  14. package/lib/browser/bufferUtils-Uc0eRItL.cjs +38 -0
  15. package/lib/browser/bufferUtils-Uc0eRItL.cjs.map +1 -0
  16. package/lib/browser/createRequestId-Cs4oXfa1.cjs +205 -0
  17. package/lib/browser/createRequestId-Cs4oXfa1.cjs.map +1 -0
  18. package/lib/browser/createRequestId-DQcIlohW.mjs +170 -0
  19. package/lib/browser/createRequestId-DQcIlohW.mjs.map +1 -0
  20. package/lib/browser/fetch-BHcqM3z7.cjs +253 -0
  21. package/lib/browser/fetch-BHcqM3z7.cjs.map +1 -0
  22. package/lib/browser/fetch-DSJoynSF.mjs +248 -0
  23. package/lib/browser/fetch-DSJoynSF.mjs.map +1 -0
  24. package/lib/browser/getRawRequest-BTaNLFr0.mjs +218 -0
  25. package/lib/browser/getRawRequest-BTaNLFr0.mjs.map +1 -0
  26. package/lib/browser/getRawRequest-zx8rUJL2.cjs +259 -0
  27. package/lib/browser/getRawRequest-zx8rUJL2.cjs.map +1 -0
  28. package/lib/browser/glossary-BdLS4k1H.d.cts +70 -0
  29. package/lib/browser/glossary-gEEJhK4S.d.mts +70 -0
  30. package/lib/browser/handleRequest-DI6a7Dty.cjs +189 -0
  31. package/lib/browser/handleRequest-DI6a7Dty.cjs.map +1 -0
  32. package/lib/browser/handleRequest-DxGbCTbb.mjs +178 -0
  33. package/lib/browser/handleRequest-DxGbCTbb.mjs.map +1 -0
  34. package/lib/browser/hasConfigurableGlobal-C8kXFDic.mjs +33 -0
  35. package/lib/browser/hasConfigurableGlobal-C8kXFDic.mjs.map +1 -0
  36. package/lib/browser/hasConfigurableGlobal-D7S3l5h6.cjs +45 -0
  37. package/lib/browser/hasConfigurableGlobal-D7S3l5h6.cjs.map +1 -0
  38. package/lib/browser/index.cjs +68 -0
  39. package/lib/browser/index.cjs.map +1 -0
  40. package/lib/browser/index.d.cts +87 -0
  41. package/lib/browser/index.d.mts +87 -0
  42. package/lib/browser/index.mjs +49 -75
  43. package/lib/browser/index.mjs.map +1 -1
  44. package/lib/browser/interceptors/WebSocket/index.cjs +621 -0
  45. package/lib/browser/interceptors/WebSocket/index.cjs.map +1 -0
  46. package/lib/browser/interceptors/WebSocket/index.d.cts +277 -0
  47. package/lib/browser/interceptors/WebSocket/index.d.mts +277 -0
  48. package/lib/browser/interceptors/WebSocket/index.mjs +587 -694
  49. package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -1
  50. package/lib/browser/interceptors/XMLHttpRequest/index.cjs +7 -0
  51. package/lib/browser/interceptors/XMLHttpRequest/index.d.cts +15 -0
  52. package/lib/browser/interceptors/XMLHttpRequest/index.d.mts +15 -0
  53. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +7 -12
  54. package/lib/browser/interceptors/fetch/index.cjs +6 -0
  55. package/lib/browser/interceptors/fetch/index.d.cts +13 -0
  56. package/lib/browser/interceptors/fetch/index.d.mts +13 -0
  57. package/lib/browser/interceptors/fetch/index.mjs +6 -11
  58. package/lib/browser/presets/browser.cjs +17 -0
  59. package/lib/browser/presets/browser.cjs.map +1 -0
  60. package/lib/browser/presets/browser.d.cts +12 -0
  61. package/lib/browser/presets/browser.d.mts +14 -0
  62. package/lib/browser/presets/browser.mjs +15 -19
  63. package/lib/browser/presets/browser.mjs.map +1 -1
  64. package/lib/node/BatchInterceptor-3LnAnLTx.cjs +49 -0
  65. package/lib/node/BatchInterceptor-3LnAnLTx.cjs.map +1 -0
  66. package/lib/node/BatchInterceptor-D7mXzHcQ.d.mts +26 -0
  67. package/lib/node/BatchInterceptor-DFaBPilf.mjs +44 -0
  68. package/lib/node/BatchInterceptor-DFaBPilf.mjs.map +1 -0
  69. package/lib/node/BatchInterceptor-D_YqR8qU.d.cts +26 -0
  70. package/lib/node/ClientRequest-2rDe54Ui.cjs +1043 -0
  71. package/lib/node/ClientRequest-2rDe54Ui.cjs.map +1 -0
  72. package/lib/node/ClientRequest-Ca8Qykuv.mjs +1034 -0
  73. package/lib/node/ClientRequest-Ca8Qykuv.mjs.map +1 -0
  74. package/lib/node/Interceptor-DEazpLJd.d.mts +133 -0
  75. package/lib/node/Interceptor-DJ2akVWI.d.cts +133 -0
  76. package/lib/node/RemoteHttpInterceptor.cjs +154 -0
  77. package/lib/node/RemoteHttpInterceptor.cjs.map +1 -0
  78. package/lib/node/RemoteHttpInterceptor.d.cts +39 -0
  79. package/lib/node/RemoteHttpInterceptor.d.mts +39 -0
  80. package/lib/node/RemoteHttpInterceptor.mjs +145 -186
  81. package/lib/node/RemoteHttpInterceptor.mjs.map +1 -1
  82. package/lib/node/XMLHttpRequest-B7kJdYYI.cjs +763 -0
  83. package/lib/node/XMLHttpRequest-B7kJdYYI.cjs.map +1 -0
  84. package/lib/node/XMLHttpRequest-C8dIZpds.mjs +757 -0
  85. package/lib/node/XMLHttpRequest-C8dIZpds.mjs.map +1 -0
  86. package/lib/node/bufferUtils-DiCTqG-7.cjs +38 -0
  87. package/lib/node/bufferUtils-DiCTqG-7.cjs.map +1 -0
  88. package/lib/node/bufferUtils-_8XfKIfX.mjs +20 -0
  89. package/lib/node/bufferUtils-_8XfKIfX.mjs.map +1 -0
  90. package/lib/node/chunk-CbDLau6x.cjs +34 -0
  91. package/lib/node/fetch-BmXpK10r.cjs +272 -0
  92. package/lib/node/fetch-BmXpK10r.cjs.map +1 -0
  93. package/lib/node/fetch-G1DVwDKG.mjs +265 -0
  94. package/lib/node/fetch-G1DVwDKG.mjs.map +1 -0
  95. package/lib/node/fetchUtils-BaY5iWXw.cjs +419 -0
  96. package/lib/node/fetchUtils-BaY5iWXw.cjs.map +1 -0
  97. package/lib/node/fetchUtils-CoU35g3M.mjs +359 -0
  98. package/lib/node/fetchUtils-CoU35g3M.mjs.map +1 -0
  99. package/lib/node/getRawRequest-BavnMWh_.cjs +36 -0
  100. package/lib/node/getRawRequest-BavnMWh_.cjs.map +1 -0
  101. package/lib/node/getRawRequest-DnwmXyOW.mjs +24 -0
  102. package/lib/node/getRawRequest-DnwmXyOW.mjs.map +1 -0
  103. package/lib/node/glossary-BLKRyLBd.cjs +12 -0
  104. package/lib/node/glossary-BLKRyLBd.cjs.map +1 -0
  105. package/lib/node/glossary-glQBRnVD.mjs +6 -0
  106. package/lib/node/glossary-glQBRnVD.mjs.map +1 -0
  107. package/lib/node/handleRequest-Bb7Y-XLw.cjs +220 -0
  108. package/lib/node/handleRequest-Bb7Y-XLw.cjs.map +1 -0
  109. package/lib/node/handleRequest-Y97UwBbF.mjs +190 -0
  110. package/lib/node/handleRequest-Y97UwBbF.mjs.map +1 -0
  111. package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs +26 -0
  112. package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs.map +1 -0
  113. package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs +20 -0
  114. package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs.map +1 -0
  115. package/lib/node/index-BMbJ8FXL.d.cts +113 -0
  116. package/lib/node/index-C0YAQ36w.d.mts +113 -0
  117. package/lib/node/index.cjs +30 -0
  118. package/lib/node/index.cjs.map +1 -0
  119. package/lib/node/index.d.cts +66 -0
  120. package/lib/node/index.d.mts +66 -0
  121. package/lib/node/index.mjs +13 -39
  122. package/lib/node/index.mjs.map +1 -1
  123. package/lib/node/interceptors/ClientRequest/index.cjs +6 -0
  124. package/lib/node/interceptors/ClientRequest/index.d.cts +2 -0
  125. package/lib/node/interceptors/ClientRequest/index.d.mts +3 -0
  126. package/lib/node/interceptors/ClientRequest/index.mjs +6 -11
  127. package/lib/node/interceptors/XMLHttpRequest/index.cjs +6 -0
  128. package/lib/node/interceptors/XMLHttpRequest/index.d.cts +14 -0
  129. package/lib/node/interceptors/XMLHttpRequest/index.d.mts +14 -0
  130. package/lib/node/interceptors/XMLHttpRequest/index.mjs +6 -13
  131. package/lib/node/interceptors/fetch/index.cjs +5 -0
  132. package/lib/node/interceptors/fetch/index.d.cts +12 -0
  133. package/lib/node/interceptors/fetch/index.d.mts +12 -0
  134. package/lib/node/interceptors/fetch/index.mjs +5 -12
  135. package/lib/node/node-DwCc6iuP.mjs +27 -0
  136. package/lib/node/node-DwCc6iuP.mjs.map +1 -0
  137. package/lib/node/node-dKdAf3tC.cjs +39 -0
  138. package/lib/node/node-dKdAf3tC.cjs.map +1 -0
  139. package/lib/node/presets/node.cjs +22 -0
  140. package/lib/node/presets/node.cjs.map +1 -0
  141. package/lib/node/presets/node.d.cts +13 -0
  142. package/lib/node/presets/node.d.mts +15 -0
  143. package/lib/node/presets/node.mjs +18 -23
  144. package/lib/node/presets/node.mjs.map +1 -1
  145. package/lib/node/utils/node/index.cjs +4 -0
  146. package/lib/node/utils/node/{index.d.ts → index.d.cts} +5 -2
  147. package/lib/node/utils/node/index.d.mts +16 -0
  148. package/lib/node/utils/node/index.mjs +3 -10
  149. package/package.json +34 -59
  150. package/presets/browser/package.json +2 -3
  151. package/presets/node/package.json +7 -2
  152. package/src/RemoteHttpInterceptor.ts +18 -13
  153. package/src/RequestController.test.ts +78 -31
  154. package/src/RequestController.ts +63 -39
  155. package/src/index.ts +4 -0
  156. package/src/interceptors/ClientRequest/MockHttpSocket.ts +43 -9
  157. package/src/interceptors/ClientRequest/index.ts +14 -18
  158. package/src/interceptors/ClientRequest/utils/parserUtils.ts +48 -0
  159. package/src/interceptors/WebSocket/index.ts +4 -1
  160. package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +45 -35
  161. package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +24 -21
  162. package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.test.ts +2 -2
  163. package/src/interceptors/fetch/index.ts +61 -50
  164. package/src/utils/handleRequest.ts +65 -95
  165. package/lib/browser/Interceptor-af98b768.d.ts +0 -63
  166. package/lib/browser/chunk-2HUMWGRD.js +0 -37
  167. package/lib/browser/chunk-2HUMWGRD.js.map +0 -1
  168. package/lib/browser/chunk-2QICSCCS.js +0 -238
  169. package/lib/browser/chunk-2QICSCCS.js.map +0 -1
  170. package/lib/browser/chunk-3RXCRGL2.mjs +0 -117
  171. package/lib/browser/chunk-3RXCRGL2.mjs.map +0 -1
  172. package/lib/browser/chunk-6HYIRFX2.mjs +0 -22
  173. package/lib/browser/chunk-6HYIRFX2.mjs.map +0 -1
  174. package/lib/browser/chunk-E3CCOBRX.js +0 -846
  175. package/lib/browser/chunk-E3CCOBRX.js.map +0 -1
  176. package/lib/browser/chunk-E7UVBHVO.mjs +0 -846
  177. package/lib/browser/chunk-E7UVBHVO.mjs.map +0 -1
  178. package/lib/browser/chunk-H74PGQ4Y.js +0 -296
  179. package/lib/browser/chunk-H74PGQ4Y.js.map +0 -1
  180. package/lib/browser/chunk-LK6DILFK.js +0 -22
  181. package/lib/browser/chunk-LK6DILFK.js.map +0 -1
  182. package/lib/browser/chunk-PTTUYYVR.mjs +0 -238
  183. package/lib/browser/chunk-PTTUYYVR.mjs.map +0 -1
  184. package/lib/browser/chunk-Q7K2XAEP.mjs +0 -296
  185. package/lib/browser/chunk-Q7K2XAEP.mjs.map +0 -1
  186. package/lib/browser/chunk-QED3Q6Z2.mjs +0 -169
  187. package/lib/browser/chunk-QED3Q6Z2.mjs.map +0 -1
  188. package/lib/browser/chunk-T7TBRNJZ.js +0 -117
  189. package/lib/browser/chunk-T7TBRNJZ.js.map +0 -1
  190. package/lib/browser/chunk-TIPR373R.js +0 -169
  191. package/lib/browser/chunk-TIPR373R.js.map +0 -1
  192. package/lib/browser/chunk-VYSDLBSS.mjs +0 -37
  193. package/lib/browser/chunk-VYSDLBSS.mjs.map +0 -1
  194. package/lib/browser/glossary-7152281e.d.ts +0 -69
  195. package/lib/browser/index.d.ts +0 -83
  196. package/lib/browser/index.js +0 -81
  197. package/lib/browser/index.js.map +0 -1
  198. package/lib/browser/interceptors/WebSocket/index.d.ts +0 -271
  199. package/lib/browser/interceptors/WebSocket/index.js +0 -721
  200. package/lib/browser/interceptors/WebSocket/index.js.map +0 -1
  201. package/lib/browser/interceptors/XMLHttpRequest/index.d.ts +0 -15
  202. package/lib/browser/interceptors/XMLHttpRequest/index.js +0 -12
  203. package/lib/browser/interceptors/XMLHttpRequest/index.js.map +0 -1
  204. package/lib/browser/interceptors/XMLHttpRequest/index.mjs.map +0 -1
  205. package/lib/browser/interceptors/fetch/index.d.ts +0 -14
  206. package/lib/browser/interceptors/fetch/index.js +0 -11
  207. package/lib/browser/interceptors/fetch/index.js.map +0 -1
  208. package/lib/browser/interceptors/fetch/index.mjs.map +0 -1
  209. package/lib/browser/presets/browser.d.ts +0 -15
  210. package/lib/browser/presets/browser.js +0 -21
  211. package/lib/browser/presets/browser.js.map +0 -1
  212. package/lib/node/BatchInterceptor-5b72232f.d.ts +0 -24
  213. package/lib/node/Interceptor-bc5a9d8e.d.ts +0 -130
  214. package/lib/node/RemoteHttpInterceptor.d.ts +0 -45
  215. package/lib/node/RemoteHttpInterceptor.js +0 -193
  216. package/lib/node/RemoteHttpInterceptor.js.map +0 -1
  217. package/lib/node/chunk-3CNGDJFB.mjs +0 -313
  218. package/lib/node/chunk-3CNGDJFB.mjs.map +0 -1
  219. package/lib/node/chunk-3GJB4JDF.mjs +0 -14
  220. package/lib/node/chunk-3GJB4JDF.mjs.map +0 -1
  221. package/lib/node/chunk-4NEYTVWD.mjs +0 -848
  222. package/lib/node/chunk-4NEYTVWD.mjs.map +0 -1
  223. package/lib/node/chunk-4YBV77DG.js +0 -32
  224. package/lib/node/chunk-4YBV77DG.js.map +0 -1
  225. package/lib/node/chunk-6HYIRFX2.mjs +0 -22
  226. package/lib/node/chunk-6HYIRFX2.mjs.map +0 -1
  227. package/lib/node/chunk-6YM4PLBI.mjs +0 -7
  228. package/lib/node/chunk-6YM4PLBI.mjs.map +0 -1
  229. package/lib/node/chunk-72ZIHMEB.js +0 -249
  230. package/lib/node/chunk-72ZIHMEB.js.map +0 -1
  231. package/lib/node/chunk-73NOP3T5.js +0 -7
  232. package/lib/node/chunk-73NOP3T5.js.map +0 -1
  233. package/lib/node/chunk-A7Q4RTDJ.mjs +0 -249
  234. package/lib/node/chunk-A7Q4RTDJ.mjs.map +0 -1
  235. package/lib/node/chunk-A7U44ARP.js +0 -268
  236. package/lib/node/chunk-A7U44ARP.js.map +0 -1
  237. package/lib/node/chunk-EKNRB5ZS.mjs +0 -1115
  238. package/lib/node/chunk-EKNRB5ZS.mjs.map +0 -1
  239. package/lib/node/chunk-IHJSPMYM.mjs +0 -268
  240. package/lib/node/chunk-IHJSPMYM.mjs.map +0 -1
  241. package/lib/node/chunk-LK6DILFK.js +0 -22
  242. package/lib/node/chunk-LK6DILFK.js.map +0 -1
  243. package/lib/node/chunk-N4ZZFE24.js +0 -1115
  244. package/lib/node/chunk-N4ZZFE24.js.map +0 -1
  245. package/lib/node/chunk-PFGO5BSM.js +0 -25
  246. package/lib/node/chunk-PFGO5BSM.js.map +0 -1
  247. package/lib/node/chunk-R6JVCM7X.js +0 -51
  248. package/lib/node/chunk-R6JVCM7X.js.map +0 -1
  249. package/lib/node/chunk-RC2XPCC4.mjs +0 -51
  250. package/lib/node/chunk-RC2XPCC4.mjs.map +0 -1
  251. package/lib/node/chunk-SMXZPJEA.js +0 -14
  252. package/lib/node/chunk-SMXZPJEA.js.map +0 -1
  253. package/lib/node/chunk-TJDMZZXE.mjs +0 -32
  254. package/lib/node/chunk-TJDMZZXE.mjs.map +0 -1
  255. package/lib/node/chunk-TX5GBTFY.mjs +0 -25
  256. package/lib/node/chunk-TX5GBTFY.mjs.map +0 -1
  257. package/lib/node/chunk-VV2LUF5K.js +0 -848
  258. package/lib/node/chunk-VV2LUF5K.js.map +0 -1
  259. package/lib/node/chunk-Z5LWCBZS.js +0 -313
  260. package/lib/node/chunk-Z5LWCBZS.js.map +0 -1
  261. package/lib/node/index.d.ts +0 -62
  262. package/lib/node/index.js +0 -43
  263. package/lib/node/index.js.map +0 -1
  264. package/lib/node/interceptors/ClientRequest/index.d.ts +0 -111
  265. package/lib/node/interceptors/ClientRequest/index.js +0 -11
  266. package/lib/node/interceptors/ClientRequest/index.js.map +0 -1
  267. package/lib/node/interceptors/ClientRequest/index.mjs.map +0 -1
  268. package/lib/node/interceptors/XMLHttpRequest/index.d.ts +0 -14
  269. package/lib/node/interceptors/XMLHttpRequest/index.js +0 -13
  270. package/lib/node/interceptors/XMLHttpRequest/index.js.map +0 -1
  271. package/lib/node/interceptors/XMLHttpRequest/index.mjs.map +0 -1
  272. package/lib/node/interceptors/fetch/index.d.ts +0 -13
  273. package/lib/node/interceptors/fetch/index.js +0 -12
  274. package/lib/node/interceptors/fetch/index.js.map +0 -1
  275. package/lib/node/interceptors/fetch/index.mjs.map +0 -1
  276. package/lib/node/presets/node.d.ts +0 -16
  277. package/lib/node/presets/node.js +0 -27
  278. package/lib/node/presets/node.js.map +0 -1
  279. package/lib/node/utils/node/index.js +0 -10
  280. package/lib/node/utils/node/index.js.map +0 -1
  281. package/lib/node/utils/node/index.mjs.map +0 -1
  282. package/src/utils/RequestController.ts +0 -21
@@ -1,35 +1,59 @@
1
- import { invariant } from 'outvariant'
2
1
  import { DeferredPromise } from '@open-draft/deferred-promise'
2
+ import { invariant } from 'outvariant'
3
3
  import { InterceptorError } from './InterceptorError'
4
4
 
5
- const kRequestHandled = Symbol('kRequestHandled')
6
- export const kResponsePromise = Symbol('kResponsePromise')
5
+ export interface RequestControllerSource {
6
+ passthrough(): void
7
+ respondWith(response: Response): void
8
+ errorWith(reason?: unknown): void
9
+ }
7
10
 
8
11
  export class RequestController {
12
+ static PENDING = 0 as const
13
+ static PASSTHROUGH = 1 as const
14
+ static RESPONSE = 2 as const
15
+ static ERROR = 3 as const
16
+
17
+ public readyState: number
18
+
9
19
  /**
10
- * Internal response promise.
11
- * Available only for the library internals to grab the
12
- * response instance provided by the developer.
13
- * @note This promise cannot be rejected. It's either infinitely
14
- * pending or resolved with whichever Response was passed to `respondWith()`.
20
+ * A Promise that resolves when this controller handles a request.
21
+ * See `controller.readyState` for more information on the handling result.
15
22
  */
16
- [kResponsePromise]: DeferredPromise<
17
- Response | Record<string, any> | undefined
18
- >;
23
+ public handled: Promise<void>
24
+
25
+ constructor(
26
+ protected readonly request: Request,
27
+ protected readonly source: RequestControllerSource
28
+ ) {
29
+ this.readyState = RequestController.PENDING
30
+ this.handled = new DeferredPromise<void>()
31
+ }
32
+
33
+ get #handled() {
34
+ return this.handled as DeferredPromise<void>
35
+ }
19
36
 
20
37
  /**
21
- * Internal flag indicating if this request has been handled.
22
- * @note The response promise becomes "fulfilled" on the next tick.
38
+ * Perform this request as-is.
23
39
  */
24
- [kRequestHandled]: boolean
40
+ public async passthrough(): Promise<void> {
41
+ invariant.as(
42
+ InterceptorError,
43
+ this.readyState === RequestController.PENDING,
44
+ 'Failed to passthrough the "%s %s" request: the request has already been handled',
45
+ this.request.method,
46
+ this.request.url
47
+ )
25
48
 
26
- constructor(private request: Request) {
27
- this[kRequestHandled] = false
28
- this[kResponsePromise] = new DeferredPromise()
49
+ this.readyState = RequestController.PASSTHROUGH
50
+ await this.source.passthrough()
51
+ this.#handled.resolve()
29
52
  }
30
53
 
31
54
  /**
32
55
  * Respond to this request with the given `Response` instance.
56
+ *
33
57
  * @example
34
58
  * controller.respondWith(new Response())
35
59
  * controller.respondWith(Response.json({ id }))
@@ -38,22 +62,25 @@ export class RequestController {
38
62
  public respondWith(response: Response): void {
39
63
  invariant.as(
40
64
  InterceptorError,
41
- !this[kRequestHandled],
42
- 'Failed to respond to the "%s %s" request: the "request" event has already been handled.',
65
+ this.readyState === RequestController.PENDING,
66
+ 'Failed to respond to the "%s %s" request with "%d %s": the request has already been handled (%d)',
43
67
  this.request.method,
44
- this.request.url
68
+ this.request.url,
69
+ response.status,
70
+ response.statusText || 'OK',
71
+ this.readyState
45
72
  )
46
73
 
47
- this[kRequestHandled] = true
48
- this[kResponsePromise].resolve(response)
74
+ this.readyState = RequestController.RESPONSE
75
+ this.#handled.resolve()
49
76
 
50
77
  /**
51
- * @note The request controller doesn't do anything
52
- * apart from letting the interceptor await the response
53
- * provided by the developer through the response promise.
54
- * Each interceptor implements the actual respondWith/errorWith
55
- * logic based on that interceptor's needs.
78
+ * @note Although `source.respondWith()` is potentially asynchronous,
79
+ * do NOT await it for backward-compatibility. Awaiting it will short-circuit
80
+ * the request listener invocation as soon as a listener responds to a request.
81
+ * Ideally, that's what we want, but that's not what we promise the user.
56
82
  */
83
+ this.source.respondWith(response)
57
84
  }
58
85
 
59
86
  /**
@@ -64,22 +91,19 @@ export class RequestController {
64
91
  * controller.errorWith(new Error('Oops!'))
65
92
  * controller.errorWith({ message: 'Oops!'})
66
93
  */
67
- public errorWith(reason?: Error | Record<string, any>): void {
94
+ public errorWith(reason?: unknown): void {
68
95
  invariant.as(
69
96
  InterceptorError,
70
- !this[kRequestHandled],
71
- 'Failed to error the "%s %s" request: the "request" event has already been handled.',
97
+ this.readyState === RequestController.PENDING,
98
+ 'Failed to error the "%s %s" request with "%s": the request has already been handled (%d)',
72
99
  this.request.method,
73
- this.request.url
100
+ this.request.url,
101
+ reason?.toString(),
102
+ this.readyState
74
103
  )
75
104
 
76
- this[kRequestHandled] = true
77
-
78
- /**
79
- * @note Resolve the response promise, not reject.
80
- * This helps us differentiate between unhandled exceptions
81
- * and intended errors ("errorWith") while waiting for the response.
82
- */
83
- this[kResponsePromise].resolve(reason)
105
+ this.readyState = RequestController.ERROR
106
+ this.source.errorWith(reason)
107
+ this.#handled.resolve()
84
108
  }
85
109
  }
package/src/index.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export * from './glossary'
2
2
  export * from './Interceptor'
3
3
  export * from './BatchInterceptor'
4
+ export {
5
+ RequestController,
6
+ type RequestControllerSource,
7
+ } from './RequestController'
4
8
 
5
9
  /* Utils */
6
10
  export { createRequestId } from './createRequestId'
@@ -13,12 +13,12 @@ import { MockSocket } from '../Socket/MockSocket'
13
13
  import type { NormalizedSocketWriteArgs } from '../Socket/utils/normalizeSocketWriteArgs'
14
14
  import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
15
15
  import { baseUrlFromConnectionOptions } from '../Socket/utils/baseUrlFromConnectionOptions'
16
- import { createServerErrorResponse } from '../../utils/responseUtils'
17
16
  import { createRequestId } from '../../createRequestId'
18
17
  import { getRawFetchHeaders } from './utils/recordRawHeaders'
19
18
  import { FetchResponse } from '../../utils/fetchUtils'
20
19
  import { setRawRequest } from '../../getRawRequest'
21
20
  import { setRawRequestBodyStream } from '../../utils/node'
21
+ import { freeParser } from './utils/parserUtils'
22
22
 
23
23
  type HttpConnectionOptions = any
24
24
 
@@ -136,7 +136,7 @@ export class MockHttpSocket extends MockSocket {
136
136
 
137
137
  // Once the socket is finished, nothing can write to it
138
138
  // anymore. It has also flushed any buffered chunks.
139
- this.once('finish', () => this.requestParser.free())
139
+ this.once('finish', () => freeParser(this.requestParser, this))
140
140
 
141
141
  if (this.baseUrl.protocol === 'https:') {
142
142
  Reflect.set(this, 'encrypted', true)
@@ -146,6 +146,11 @@ export class MockHttpSocket extends MockSocket {
146
146
  Reflect.set(this, 'getProtocol', () => 'TLSv1.3')
147
147
  Reflect.set(this, 'getSession', () => undefined)
148
148
  Reflect.set(this, 'isSessionReused', () => false)
149
+ Reflect.set(this, 'getCipher', () => ({
150
+ name: 'AES256-SHA',
151
+ standardName: 'TLS_RSA_WITH_AES_256_CBC_SHA',
152
+ version: 'TLSv1.3',
153
+ }))
149
154
  }
150
155
  }
151
156
 
@@ -164,7 +169,7 @@ export class MockHttpSocket extends MockSocket {
164
169
  // Destroy the response parser when the socket gets destroyed.
165
170
  // Normally, we should listen to the "close" event but it
166
171
  // can be suppressed by using the "emitClose: false" option.
167
- this.responseParser.free()
172
+ freeParser(this.responseParser, this)
168
173
 
169
174
  if (error) {
170
175
  this.emit('error', error)
@@ -201,9 +206,18 @@ export class MockHttpSocket extends MockSocket {
201
206
  })
202
207
  }
203
208
 
204
- // If the developer destroys the socket, destroy the original connection.
205
- this.once('error', (error) => {
206
- socket.destroy(error)
209
+ // The client-facing socket can be destroyed in two ways:
210
+ // 1. The developer destroys the socket.
211
+ // 2. The passthrough socket "close" is forwarded to the socket.
212
+ this.once('close', () => {
213
+ socket.removeAllListeners()
214
+
215
+ // If the closure didn't originate from the passthrough socket, destroy it.
216
+ if (!socket.destroyed) {
217
+ socket.destroy()
218
+ }
219
+
220
+ this.originalSocket = undefined
207
221
  })
208
222
 
209
223
  this.address = socket.address.bind(socket)
@@ -258,6 +272,7 @@ export class MockHttpSocket extends MockSocket {
258
272
  'getProtocol',
259
273
  'getSession',
260
274
  'isSessionReused',
275
+ 'getCipher',
261
276
  ]
262
277
 
263
278
  tlsProperties.forEach((propertyName) => {
@@ -311,6 +326,16 @@ export class MockHttpSocket extends MockSocket {
311
326
  return
312
327
  }
313
328
 
329
+ // Prevent recursive calls.
330
+ invariant(
331
+ this.socketState !== 'mock',
332
+ '[MockHttpSocket] Failed to respond to the "%s %s" request with "%s %s": the request has already been handled',
333
+ this.request?.method,
334
+ this.request?.url,
335
+ response.status,
336
+ response.statusText
337
+ )
338
+
314
339
  // Handle "type: error" responses.
315
340
  if (isPropertyAccessible(response, 'type') && response.type === 'error') {
316
341
  this.errorWith(new TypeError('Network error'))
@@ -393,9 +418,18 @@ export class MockHttpSocket extends MockSocket {
393
418
  serverResponse.write(value)
394
419
  }
395
420
  } catch (error) {
396
- // Coerce response stream errors to 500 responses.
397
- this.respondWith(createServerErrorResponse(error))
398
- return
421
+ if (error instanceof Error) {
422
+ serverResponse.destroy()
423
+ /**
424
+ * @note Destroy the request socket gracefully.
425
+ * Response stream errors do NOT produce request errors.
426
+ */
427
+ this.destroy()
428
+ return
429
+ }
430
+
431
+ serverResponse.destroy()
432
+ throw error
399
433
  }
400
434
  } else {
401
435
  serverResponse.end()
@@ -153,30 +153,26 @@ export class ClientRequestInterceptor extends Interceptor<HttpRequestEventMap> {
153
153
  request,
154
154
  socket,
155
155
  }) => {
156
- const requestId = Reflect.get(request, kRequestId)
157
- const controller = new RequestController(request)
158
-
159
- const isRequestHandled = await handleRequest({
160
- request,
161
- requestId,
162
- controller,
163
- emitter: this.emitter,
164
- onResponse: (response) => {
165
- socket.respondWith(response)
156
+ const controller = new RequestController(request, {
157
+ passthrough() {
158
+ socket.passthrough()
166
159
  },
167
- onRequestError: (response) => {
168
- socket.respondWith(response)
160
+ async respondWith(response) {
161
+ await socket.respondWith(response)
169
162
  },
170
- onError: (error) => {
171
- if (error instanceof Error) {
172
- socket.errorWith(error)
163
+ errorWith(reason) {
164
+ if (reason instanceof Error) {
165
+ socket.errorWith(reason)
173
166
  }
174
167
  },
175
168
  })
176
169
 
177
- if (!isRequestHandled) {
178
- return socket.passthrough()
179
- }
170
+ await handleRequest({
171
+ request,
172
+ requestId: Reflect.get(request, kRequestId),
173
+ controller,
174
+ emitter: this.emitter,
175
+ })
180
176
  }
181
177
 
182
178
  public onResponse: MockHttpSocketResponseCallback = async ({
@@ -0,0 +1,48 @@
1
+ import type { Socket } from 'node:net'
2
+ import { HTTPParser } from '_http_common'
3
+
4
+ /**
5
+ * @see https://github.com/nodejs/node/blob/f3adc11e37b8bfaaa026ea85c1cf22e3a0e29ae9/lib/_http_common.js#L180
6
+ */
7
+ export function freeParser(parser: HTTPParser<any>, socket?: Socket): void {
8
+ if (parser._consumed) {
9
+ parser.unconsume()
10
+ }
11
+
12
+ parser._headers = []
13
+ parser._url = ''
14
+ parser.socket = null
15
+ parser.incoming = null
16
+ parser.outgoing = null
17
+ parser.maxHeaderPairs = 2000
18
+ parser._consumed = false
19
+ parser.onIncoming = null
20
+
21
+ parser[HTTPParser.kOnHeaders] = null
22
+ parser[HTTPParser.kOnHeadersComplete] = null
23
+ parser[HTTPParser.kOnMessageBegin] = null
24
+ parser[HTTPParser.kOnMessageComplete] = null
25
+ parser[HTTPParser.kOnBody] = null
26
+ parser[HTTPParser.kOnExecute] = null
27
+ parser[HTTPParser.kOnTimeout] = null
28
+
29
+ parser.remove()
30
+ parser.free()
31
+
32
+ if (socket) {
33
+ /**
34
+ * @note Unassigning the socket's parser will fail this assertion
35
+ * if there's still some data being processed on the socket:
36
+ * @see https://github.com/nodejs/node/blob/4e1f39b678b37017ac9baa0971e3aeecd3b67b51/lib/_http_client.js#L613
37
+ */
38
+ if (socket.destroyed) {
39
+ // @ts-expect-error Node.js internals.
40
+ socket.parser = null
41
+ } else {
42
+ socket.once('end', () => {
43
+ // @ts-expect-error Node.js internals.
44
+ socket.parser = null
45
+ })
46
+ }
47
+ }
48
+ }
@@ -19,7 +19,10 @@ import { bindEvent } from './utils/bindEvent'
19
19
  import { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'
20
20
  import { emitAsync } from '../../utils/emitAsync'
21
21
 
22
- export { type WebSocketData, WebSocketTransport } from './WebSocketTransport'
22
+ export {
23
+ type WebSocketData,
24
+ type WebSocketTransport,
25
+ } from './WebSocketTransport'
23
26
  export {
24
27
  WebSocketClientEventMap,
25
28
  WebSocketClientConnectionProtocol,
@@ -57,7 +57,10 @@ export class XMLHttpRequestController {
57
57
  Array<Function>
58
58
  >
59
59
 
60
- constructor(readonly initialRequest: XMLHttpRequest, public logger: Logger) {
60
+ constructor(
61
+ readonly initialRequest: XMLHttpRequest,
62
+ public logger: Logger
63
+ ) {
61
64
  this[kIsRequestHandled] = false
62
65
 
63
66
  this.events = new Map()
@@ -111,7 +114,7 @@ export class XMLHttpRequestController {
111
114
  case 'addEventListener': {
112
115
  const [eventName, listener] = args as [
113
116
  keyof XMLHttpRequestEventTargetEventMap,
114
- Function
117
+ Function,
115
118
  ]
116
119
 
117
120
  this.registerEvent(eventName, listener)
@@ -131,7 +134,7 @@ export class XMLHttpRequestController {
131
134
 
132
135
  case 'send': {
133
136
  const [body] = args as [
134
- body?: XMLHttpRequestBodyInit | Document | null
137
+ body?: XMLHttpRequestBodyInit | Document | null,
135
138
  ]
136
139
 
137
140
  this.request.addEventListener('load', () => {
@@ -166,38 +169,44 @@ export class XMLHttpRequestController {
166
169
  const fetchRequest = this.toFetchApiRequest(requestBody)
167
170
  this[kFetchRequest] = fetchRequest.clone()
168
171
 
169
- const onceRequestSettled =
170
- this.onRequest?.call(this, {
171
- request: fetchRequest,
172
- requestId: this.requestId!,
173
- }) || Promise.resolve()
174
-
175
- onceRequestSettled.finally(() => {
176
- // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.
177
- if (!this[kIsRequestHandled]) {
178
- this.logger.info(
179
- 'request callback settled but request has not been handled (readystate %d), performing as-is...',
180
- this.request.readyState
181
- )
182
-
183
- /**
184
- * @note Set the intercepted request ID on the original request in Node.js
185
- * so that if it triggers any other interceptors, they don't attempt
186
- * to process it once again.
187
- *
188
- * For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
189
- * and we don't want for both XHR and ClientRequest interceptors to
190
- * handle the same request at the same time (e.g. emit the "response" event twice).
191
- */
192
- if (IS_NODE) {
193
- this.request.setRequestHeader(
194
- INTERNAL_REQUEST_ID_HEADER_NAME,
195
- this.requestId!
172
+ /**
173
+ * @note Start request handling on the next tick so that the user
174
+ * could add event listeners for "loadend" before the interceptor fires it.
175
+ */
176
+ queueMicrotask(() => {
177
+ const onceRequestSettled =
178
+ this.onRequest?.call(this, {
179
+ request: fetchRequest,
180
+ requestId: this.requestId!,
181
+ }) || Promise.resolve()
182
+
183
+ onceRequestSettled.finally(() => {
184
+ // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.
185
+ if (!this[kIsRequestHandled]) {
186
+ this.logger.info(
187
+ 'request callback settled but request has not been handled (readystate %d), performing as-is...',
188
+ this.request.readyState
196
189
  )
197
- }
198
190
 
199
- return invoke()
200
- }
191
+ /**
192
+ * @note Set the intercepted request ID on the original request in Node.js
193
+ * so that if it triggers any other interceptors, they don't attempt
194
+ * to process it once again.
195
+ *
196
+ * For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
197
+ * and we don't want for both XHR and ClientRequest interceptors to
198
+ * handle the same request at the same time (e.g. emit the "response" event twice).
199
+ */
200
+ if (IS_NODE) {
201
+ this.request.setRequestHeader(
202
+ INTERNAL_REQUEST_ID_HEADER_NAME,
203
+ this.requestId!
204
+ )
205
+ }
206
+
207
+ return invoke()
208
+ }
209
+ })
201
210
  })
202
211
 
203
212
  break
@@ -241,7 +250,7 @@ export class XMLHttpRequestController {
241
250
  case 'addEventListener': {
242
251
  const [eventName, listener] = args as [
243
252
  keyof XMLHttpRequestEventTargetEventMap,
244
- Function
253
+ Function,
245
254
  ]
246
255
  this.registerUploadEvent(eventName, listener)
247
256
  this.logger.info('upload.addEventListener', eventName, listener)
@@ -312,6 +321,7 @@ export class XMLHttpRequestController {
312
321
  loaded: totalRequestBodyLength,
313
322
  total: totalRequestBodyLength,
314
323
  })
324
+
315
325
  this.trigger('loadend', this.request.upload, {
316
326
  loaded: totalRequestBodyLength,
317
327
  total: totalRequestBodyLength,
@@ -614,7 +624,7 @@ export class XMLHttpRequestController {
614
624
  private trigger<
615
625
  EventName extends keyof (XMLHttpRequestEventTargetEventMap & {
616
626
  readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
617
- })
627
+ }),
618
628
  >(
619
629
  eventName: EventName,
620
630
  target: XMLHttpRequest | XMLHttpRequestUpload,
@@ -3,6 +3,7 @@ import { XMLHttpRequestEmitter } from '.'
3
3
  import { RequestController } from '../../RequestController'
4
4
  import { XMLHttpRequestController } from './XMLHttpRequestController'
5
5
  import { handleRequest } from '../../utils/handleRequest'
6
+ import { isResponseError } from '../../utils/responseUtils'
6
7
 
7
8
  export interface XMLHttpRequestProxyOptions {
8
9
  emitter: XMLHttpRequestEmitter
@@ -52,7 +53,28 @@ export function createXMLHttpRequestProxy({
52
53
  )
53
54
 
54
55
  xhrRequestController.onRequest = async function ({ request, requestId }) {
55
- const controller = new RequestController(request)
56
+ const controller = new RequestController(request, {
57
+ passthrough: () => {
58
+ this.logger.info(
59
+ 'no mocked response received, performing request as-is...'
60
+ )
61
+ },
62
+ respondWith: async (response) => {
63
+ if (isResponseError(response)) {
64
+ this.errorWith(new TypeError('Network error'))
65
+ return
66
+ }
67
+
68
+ await this.respondWith(response)
69
+ },
70
+ errorWith: (reason) => {
71
+ this.logger.info('request errored!', { error: reason })
72
+
73
+ if (reason instanceof Error) {
74
+ this.errorWith(reason)
75
+ }
76
+ },
77
+ })
56
78
 
57
79
  this.logger.info('awaiting mocked response...')
58
80
 
@@ -61,31 +83,12 @@ export function createXMLHttpRequestProxy({
61
83
  emitter.listenerCount('request')
62
84
  )
63
85
 
64
- const isRequestHandled = await handleRequest({
86
+ await handleRequest({
65
87
  request,
66
88
  requestId,
67
89
  controller,
68
90
  emitter,
69
- onResponse: async (response) => {
70
- await this.respondWith(response)
71
- },
72
- onRequestError: () => {
73
- this.errorWith(new TypeError('Network error'))
74
- },
75
- onError: (error) => {
76
- this.logger.info('request errored!', { error })
77
-
78
- if (error instanceof Error) {
79
- this.errorWith(error)
80
- }
81
- },
82
91
  })
83
-
84
- if (!isRequestHandled) {
85
- this.logger.info(
86
- 'no mocked response received, performing request as-is...'
87
- )
88
- }
89
92
  }
90
93
 
91
94
  xhrRequestController.onResponse = async function ({
@@ -88,7 +88,7 @@ it('calculates body length from the FormData request body', async () => {
88
88
  body: formData,
89
89
  })
90
90
  )
91
- ).resolves.toBe(127)
91
+ ).resolves.toBe(129)
92
92
  })
93
93
 
94
94
  it('calculates body length from the ReadableStream request body', async () => {
@@ -149,7 +149,7 @@ it('calculates body length from the FormData response body', async () => {
149
149
  const formData = new FormData()
150
150
  formData.append('hello', 'world')
151
151
 
152
- await expect(getBodyByteLength(new Response(formData))).resolves.toBe(127)
152
+ await expect(getBodyByteLength(new Response(formData))).resolves.toBe(129)
153
153
  })
154
154
 
155
155
  it('calculates body length from the ReadableStream response body', async () => {