@mswjs/interceptors 0.41.9 → 0.42.1

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 (335) hide show
  1. package/README.md +274 -228
  2. package/lib/browser/{bufferUtils-BiiO6HZv.mjs → buffer-utils-DJj7YzLG.js} +3 -3
  3. package/lib/browser/buffer-utils-DJj7YzLG.js.map +1 -0
  4. package/lib/browser/create-request-id-Bk5YX1AM.js +720 -0
  5. package/lib/browser/create-request-id-Bk5YX1AM.js.map +1 -0
  6. package/lib/browser/{getRawRequest-B1BqgWG6.mjs → fetch-utils-CUOrwEQf.js} +123 -52
  7. package/lib/browser/fetch-utils-CUOrwEQf.js.map +1 -0
  8. package/lib/browser/handle-request-CqVvBhcw.js +167 -0
  9. package/lib/browser/handle-request-CqVvBhcw.js.map +1 -0
  10. package/lib/{node/hasConfigurableGlobal-BiTmog1u.mjs → browser/has-configurable-global-Cew-dYqk.js} +5 -7
  11. package/lib/browser/has-configurable-global-Cew-dYqk.js.map +1 -0
  12. package/lib/browser/http-CB9rQ7i_.d.ts +105 -0
  13. package/lib/browser/{index.d.cts → index.d.ts} +33 -38
  14. package/lib/browser/index.js +130 -0
  15. package/lib/browser/index.js.map +1 -0
  16. package/lib/browser/interceptor-dtJZ_9QT.d.ts +50 -0
  17. package/lib/browser/interceptors/WebSocket/index.d.ts +2 -0
  18. package/lib/browser/interceptors/WebSocket/{index.mjs → index.js} +39 -30
  19. package/lib/browser/interceptors/WebSocket/index.js.map +1 -0
  20. package/lib/browser/interceptors/XMLHttpRequest/web.d.ts +11 -0
  21. package/lib/browser/interceptors/XMLHttpRequest/web.js +2 -0
  22. package/lib/browser/interceptors/fetch/web.d.ts +14 -0
  23. package/lib/browser/interceptors/fetch/web.js +2 -0
  24. package/lib/browser/presets/browser.d.ts +11 -0
  25. package/lib/browser/presets/browser.js +12 -0
  26. package/lib/browser/presets/browser.js.map +1 -0
  27. package/lib/browser/{resolveWebSocketUrl-C83-x9iE.mjs → resolve-web-socket-url-CSvNPLGi.js} +3 -3
  28. package/lib/browser/resolve-web-socket-url-CSvNPLGi.js.map +1 -0
  29. package/lib/browser/{XMLHttpRequest-DrZAf3w6.mjs → web-BcKSQghf.js} +327 -255
  30. package/lib/browser/web-BcKSQghf.js.map +1 -0
  31. package/lib/browser/web-CdcYFjgm.js +248 -0
  32. package/lib/browser/web-CdcYFjgm.js.map +1 -0
  33. package/lib/browser/{interceptors/WebSocket/index.d.cts → websocket-CC0nB0md.d.ts} +41 -33
  34. package/lib/node/batch-interceptor-ByEZVih3.js +119 -0
  35. package/lib/node/batch-interceptor-ByEZVih3.js.map +1 -0
  36. package/lib/node/batch-interceptor-CPjFK2td.d.ts +28 -0
  37. package/lib/node/buffer-utils-BvPY1Tc-.js +187 -0
  38. package/lib/node/buffer-utils-BvPY1Tc-.js.map +1 -0
  39. package/lib/node/fetch-utils-Dw1PsmtX.js +568 -0
  40. package/lib/node/fetch-utils-Dw1PsmtX.js.map +1 -0
  41. package/lib/node/has-configurable-global-CJCh_g1I.js +22 -0
  42. package/lib/node/has-configurable-global-CJCh_g1I.js.map +1 -0
  43. package/lib/node/http-CB9rQ7i_.d.ts +105 -0
  44. package/lib/{browser/interceptors/WebSocket/index.d.mts → node/index.d.ts} +120 -60
  45. package/lib/node/{index.mjs → index.js} +8 -11
  46. package/lib/node/index.js.map +1 -0
  47. package/lib/node/interceptor-dtJZ_9QT.d.ts +50 -0
  48. package/lib/node/interceptors/ClientRequest/index.d.ts +14 -0
  49. package/lib/node/interceptors/ClientRequest/index.js +67 -0
  50. package/lib/node/interceptors/ClientRequest/index.js.map +1 -0
  51. package/lib/node/interceptors/XMLHttpRequest/node.d.ts +12 -0
  52. package/lib/node/interceptors/XMLHttpRequest/node.js +74 -0
  53. package/lib/node/interceptors/XMLHttpRequest/node.js.map +1 -0
  54. package/lib/node/interceptors/fetch/node.d.ts +18 -0
  55. package/lib/node/interceptors/fetch/node.js +83 -0
  56. package/lib/node/interceptors/fetch/node.js.map +1 -0
  57. package/lib/node/interceptors/http/index.d.ts +14 -0
  58. package/lib/node/interceptors/http/index.js +32 -0
  59. package/lib/node/interceptors/http/index.js.map +1 -0
  60. package/lib/node/interceptors/net/index.d.ts +203 -0
  61. package/lib/node/interceptors/net/index.js +2 -0
  62. package/lib/node/llhttp/constants.cjs +485 -0
  63. package/lib/node/llhttp/constants.d.cts +957 -0
  64. package/lib/node/llhttp/llhttp.wasm +0 -0
  65. package/lib/node/net-DtMnyEeg.js +1388 -0
  66. package/lib/node/net-DtMnyEeg.js.map +1 -0
  67. package/lib/node/presets/node.d.ts +12 -0
  68. package/lib/node/presets/node.js +17 -0
  69. package/lib/node/presets/node.js.map +1 -0
  70. package/lib/node/{RemoteHttpInterceptor.d.cts → remote-http-interceptor.d.ts} +12 -11
  71. package/lib/node/{RemoteHttpInterceptor.mjs → remote-http-interceptor.js} +38 -33
  72. package/lib/node/remote-http-interceptor.js.map +1 -0
  73. package/lib/node/source-lP0yyEtA.js +1990 -0
  74. package/lib/node/source-lP0yyEtA.js.map +1 -0
  75. package/package.json +79 -118
  76. package/src/batch-interceptor.test.ts +475 -0
  77. package/src/batch-interceptor.ts +244 -0
  78. package/src/{createRequestId.test.ts → create-request-id.test.ts} +1 -2
  79. package/src/disposable.ts +13 -0
  80. package/src/events/http.ts +92 -0
  81. package/src/events/websocket.ts +47 -0
  82. package/src/glossary.ts +1 -27
  83. package/src/index.ts +18 -10
  84. package/src/interceptor.test.ts +279 -0
  85. package/src/interceptor.ts +139 -0
  86. package/src/interceptors/ClientRequest/index.test.ts +21 -39
  87. package/src/interceptors/ClientRequest/index.ts +64 -179
  88. package/src/interceptors/ClientRequest/utils/{recordRawHeaders.test.ts → record-raw-headers.test.ts} +1 -2
  89. package/src/interceptors/ClientRequest/utils/{recordRawHeaders.ts → record-raw-headers.ts} +29 -2
  90. package/src/interceptors/WebSocket/index.ts +33 -50
  91. package/src/interceptors/WebSocket/utils/{bindEvent.test.ts → bind-event.test.ts} +1 -2
  92. package/src/interceptors/WebSocket/utils/events.test.ts +0 -1
  93. package/src/interceptors/WebSocket/{WebSocketClassTransport.ts → web-socket-class-transport.ts} +3 -3
  94. package/src/interceptors/WebSocket/{WebSocketClientConnection.ts → web-socket-client-connection.ts} +4 -4
  95. package/src/interceptors/WebSocket/{WebSocketOverride.ts → web-socket-override.ts} +6 -7
  96. package/src/interceptors/WebSocket/{WebSocketServerConnection.ts → web-socket-server-connection.ts} +4 -4
  97. package/src/interceptors/WebSocket/{WebSocketTransport.ts → web-socket-transport.ts} +1 -1
  98. package/src/interceptors/XMLHttpRequest/node.ts +104 -0
  99. package/src/interceptors/XMLHttpRequest/polyfills/{ProgressEventPolyfill.ts → progress-event-polyfill.ts} +1 -1
  100. package/src/interceptors/XMLHttpRequest/utils/{concateArrayBuffer.test.ts → concat-array-buffer.test.ts} +1 -2
  101. package/src/interceptors/XMLHttpRequest/utils/{concatArrayBuffer.ts → concat-array-buffer.ts} +1 -1
  102. package/src/interceptors/XMLHttpRequest/utils/{createEvent.test.ts → create-event.test.ts} +3 -4
  103. package/src/interceptors/XMLHttpRequest/utils/{createEvent.ts → create-event.ts} +2 -2
  104. package/src/interceptors/XMLHttpRequest/utils/{createResponse.ts → create-response.ts} +1 -1
  105. package/src/interceptors/XMLHttpRequest/utils/{getBodyByteLength.test.ts → get-body-byte-length.test.ts} +1 -2
  106. package/src/interceptors/XMLHttpRequest/web.ts +34 -0
  107. package/src/interceptors/XMLHttpRequest/{XMLHttpRequestController.ts → xml-http-request-controller.ts} +363 -204
  108. package/src/interceptors/XMLHttpRequest/{XMLHttpRequestProxy.ts → xml-http-request-proxy.ts} +28 -25
  109. package/src/interceptors/fetch/node.ts +103 -0
  110. package/src/interceptors/fetch/utils/{followRedirect.ts → follow-redirect.ts} +1 -1
  111. package/src/interceptors/fetch/web.ts +217 -0
  112. package/src/interceptors/http/forward-events.test.ts +86 -0
  113. package/src/interceptors/http/forward-events.ts +139 -0
  114. package/src/interceptors/http/http-parser/index.ts +310 -0
  115. package/src/interceptors/http/http-parser/llhttp/constants.cjs +485 -0
  116. package/src/interceptors/http/http-parser/llhttp/constants.d.cts +957 -0
  117. package/src/interceptors/http/http-parser/llhttp/llhttp.wasm +0 -0
  118. package/src/interceptors/http/http-parser.ts +138 -0
  119. package/src/interceptors/http/index.ts +34 -0
  120. package/src/interceptors/http/source.ts +688 -0
  121. package/src/interceptors/net/index.ts +368 -0
  122. package/src/interceptors/net/socket-controller.ts +1476 -0
  123. package/src/interceptors/net/utils/address-info.ts +54 -0
  124. package/src/interceptors/net/utils/connection-options-to-url.ts +56 -0
  125. package/src/interceptors/net/utils/flush-writes.ts +57 -0
  126. package/src/interceptors/net/utils/get-tls-connect-options.ts +23 -0
  127. package/src/interceptors/net/utils/normalize-net-connect-args.test.ts +233 -0
  128. package/src/interceptors/net/utils/normalize-net-connect-args.ts +121 -0
  129. package/src/interceptors/net/utils/normalize-tls-connect-args.ts +50 -0
  130. package/src/presets/browser.ts +4 -4
  131. package/src/presets/node.ts +4 -4
  132. package/src/{RemoteHttpInterceptor.ts → remote-http-interceptor.ts} +55 -42
  133. package/src/request-context.ts +40 -0
  134. package/src/{RequestController.test.ts → request-controller.test.ts} +37 -3
  135. package/src/{RequestController.ts → request-controller.ts} +33 -11
  136. package/src/utils/{bufferUtils.test.ts → buffer-utils.test.ts} +1 -2
  137. package/src/utils/buffer-utils.ts +37 -0
  138. package/src/utils/{cloneObject.test.ts → clone-object.test.ts} +1 -2
  139. package/src/utils/{cloneObject.ts → clone-object.ts} +8 -8
  140. package/src/utils/{createProxy.test.ts → create-proxy.test.ts} +6 -4
  141. package/src/utils/{createProxy.ts → create-proxy.ts} +1 -1
  142. package/src/utils/{fetchUtils.test.ts → fetch-utils.test.ts} +1 -1
  143. package/src/utils/{fetchUtils.ts → fetch-utils.ts} +28 -3
  144. package/src/utils/{findPropertySource.test.ts → find-property-source.test.ts} +1 -2
  145. package/src/utils/{getCleanUrl.test.ts → get-clean-url.test.ts} +1 -2
  146. package/src/utils/{getUrlByRequestOptions.test.ts → get-url-by-request-options.test.ts} +4 -8
  147. package/src/utils/{getUrlByRequestOptions.ts → get-url-by-request-options.ts} +16 -16
  148. package/src/utils/{getValueBySymbol.test.ts → get-value-by-symbol.test.ts} +1 -2
  149. package/src/utils/{handleRequest.ts → handle-request.ts} +61 -41
  150. package/src/utils/{hasConfigurableGlobal.test.ts → has-configurable-global.test.ts} +1 -2
  151. package/src/utils/{hasConfigurableGlobal.ts → has-configurable-global.ts} +2 -4
  152. package/src/utils/{isObject.test.ts → is-object.test.ts} +1 -2
  153. package/src/utils/logger.test.ts +154 -0
  154. package/src/utils/logger.ts +154 -0
  155. package/src/utils/{parseJson.test.ts → parse-json.test.ts} +1 -2
  156. package/src/utils/{patchesRegistry.test.ts → patches-registry.test.ts} +1 -1
  157. package/src/utils/{patchesRegistry.ts → patches-registry.ts} +1 -1
  158. package/src/utils/{responseUtils.ts → response-utils.ts} +25 -2
  159. package/ClientRequest/package.json +0 -11
  160. package/RemoteHttpInterceptor/package.json +0 -11
  161. package/WebSocket/package.json +0 -12
  162. package/XMLHttpRequest/package.json +0 -12
  163. package/fetch/package.json +0 -12
  164. package/lib/browser/Interceptor-Deczogc8.d.cts +0 -65
  165. package/lib/browser/Interceptor-gqKgs-aF.d.mts +0 -65
  166. package/lib/browser/XMLHttpRequest-DrZAf3w6.mjs.map +0 -1
  167. package/lib/browser/XMLHttpRequest-gXuzzX1g.cjs +0 -751
  168. package/lib/browser/XMLHttpRequest-gXuzzX1g.cjs.map +0 -1
  169. package/lib/browser/bufferUtils-BiiO6HZv.mjs.map +0 -1
  170. package/lib/browser/bufferUtils-Uc0eRItL.cjs +0 -38
  171. package/lib/browser/bufferUtils-Uc0eRItL.cjs.map +0 -1
  172. package/lib/browser/createRequestId-DOf8Ktjs.cjs +0 -205
  173. package/lib/browser/createRequestId-DOf8Ktjs.cjs.map +0 -1
  174. package/lib/browser/createRequestId-DYCsFHOi.mjs +0 -170
  175. package/lib/browser/createRequestId-DYCsFHOi.mjs.map +0 -1
  176. package/lib/browser/fetch-5IMPqr9e.mjs +0 -236
  177. package/lib/browser/fetch-5IMPqr9e.mjs.map +0 -1
  178. package/lib/browser/fetch-Bt9rMY9c.cjs +0 -241
  179. package/lib/browser/fetch-Bt9rMY9c.cjs.map +0 -1
  180. package/lib/browser/getRawRequest-B1BqgWG6.mjs.map +0 -1
  181. package/lib/browser/getRawRequest-DdfaiPVH.cjs +0 -376
  182. package/lib/browser/getRawRequest-DdfaiPVH.cjs.map +0 -1
  183. package/lib/browser/glossary-1lhZx6Aw.d.cts +0 -69
  184. package/lib/browser/glossary-nkHWcwvY.d.mts +0 -69
  185. package/lib/browser/handleRequest-CJim54xG.cjs +0 -191
  186. package/lib/browser/handleRequest-CJim54xG.cjs.map +0 -1
  187. package/lib/browser/handleRequest-CPPgsG7s.mjs +0 -180
  188. package/lib/browser/handleRequest-CPPgsG7s.mjs.map +0 -1
  189. package/lib/browser/hasConfigurableGlobal-C8zq1MCg.mjs +0 -109
  190. package/lib/browser/hasConfigurableGlobal-C8zq1MCg.mjs.map +0 -1
  191. package/lib/browser/hasConfigurableGlobal-CS7adEvV.cjs +0 -126
  192. package/lib/browser/hasConfigurableGlobal-CS7adEvV.cjs.map +0 -1
  193. package/lib/browser/index.cjs +0 -70
  194. package/lib/browser/index.cjs.map +0 -1
  195. package/lib/browser/index.d.mts +0 -125
  196. package/lib/browser/index.mjs +0 -56
  197. package/lib/browser/index.mjs.map +0 -1
  198. package/lib/browser/interceptors/WebSocket/index.cjs +0 -618
  199. package/lib/browser/interceptors/WebSocket/index.cjs.map +0 -1
  200. package/lib/browser/interceptors/WebSocket/index.mjs.map +0 -1
  201. package/lib/browser/interceptors/XMLHttpRequest/index.cjs +0 -8
  202. package/lib/browser/interceptors/XMLHttpRequest/index.d.cts +0 -15
  203. package/lib/browser/interceptors/XMLHttpRequest/index.d.mts +0 -15
  204. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +0 -8
  205. package/lib/browser/interceptors/fetch/index.cjs +0 -7
  206. package/lib/browser/interceptors/fetch/index.d.cts +0 -13
  207. package/lib/browser/interceptors/fetch/index.d.mts +0 -13
  208. package/lib/browser/interceptors/fetch/index.mjs +0 -7
  209. package/lib/browser/presets/browser.cjs +0 -18
  210. package/lib/browser/presets/browser.cjs.map +0 -1
  211. package/lib/browser/presets/browser.d.cts +0 -12
  212. package/lib/browser/presets/browser.d.mts +0 -14
  213. package/lib/browser/presets/browser.mjs +0 -18
  214. package/lib/browser/presets/browser.mjs.map +0 -1
  215. package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs +0 -31
  216. package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs.map +0 -1
  217. package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs.map +0 -1
  218. package/lib/node/BatchInterceptor-BGhH8z1z.cjs +0 -49
  219. package/lib/node/BatchInterceptor-BGhH8z1z.cjs.map +0 -1
  220. package/lib/node/BatchInterceptor-C4-RX5rg.mjs +0 -44
  221. package/lib/node/BatchInterceptor-C4-RX5rg.mjs.map +0 -1
  222. package/lib/node/BatchInterceptor-KKF8Q47I.d.cts +0 -26
  223. package/lib/node/BatchInterceptor-rSr-NH_H.d.mts +0 -26
  224. package/lib/node/ClientRequest-2loAH7xM.cjs +0 -1058
  225. package/lib/node/ClientRequest-2loAH7xM.cjs.map +0 -1
  226. package/lib/node/ClientRequest-DGhBX21V.mjs +0 -1049
  227. package/lib/node/ClientRequest-DGhBX21V.mjs.map +0 -1
  228. package/lib/node/Interceptor-BsZ21ue0.d.cts +0 -132
  229. package/lib/node/Interceptor-soD-WpQO.d.mts +0 -132
  230. package/lib/node/RemoteHttpInterceptor.cjs +0 -155
  231. package/lib/node/RemoteHttpInterceptor.cjs.map +0 -1
  232. package/lib/node/RemoteHttpInterceptor.d.mts +0 -39
  233. package/lib/node/RemoteHttpInterceptor.mjs.map +0 -1
  234. package/lib/node/XMLHttpRequest-BtDNZipW.cjs +0 -752
  235. package/lib/node/XMLHttpRequest-BtDNZipW.cjs.map +0 -1
  236. package/lib/node/XMLHttpRequest-Dw6Wm-UU.mjs +0 -746
  237. package/lib/node/XMLHttpRequest-Dw6Wm-UU.mjs.map +0 -1
  238. package/lib/node/bufferUtils-DxPxwff_.mjs +0 -20
  239. package/lib/node/bufferUtils-DxPxwff_.mjs.map +0 -1
  240. package/lib/node/bufferUtils-S5_-2eN4.cjs +0 -38
  241. package/lib/node/bufferUtils-S5_-2eN4.cjs.map +0 -1
  242. package/lib/node/chunk-CbDLau6x.cjs +0 -34
  243. package/lib/node/fetch-CaC3rGEu.mjs +0 -252
  244. package/lib/node/fetch-CaC3rGEu.mjs.map +0 -1
  245. package/lib/node/fetch-D2ClLNN-.cjs +0 -259
  246. package/lib/node/fetch-D2ClLNN-.cjs.map +0 -1
  247. package/lib/node/fetchUtils-BKJ1XmiO.mjs +0 -480
  248. package/lib/node/fetchUtils-BKJ1XmiO.mjs.map +0 -1
  249. package/lib/node/fetchUtils-umV5xXBy.cjs +0 -546
  250. package/lib/node/fetchUtils-umV5xXBy.cjs.map +0 -1
  251. package/lib/node/getRawRequest-B6znU6il.cjs +0 -36
  252. package/lib/node/getRawRequest-B6znU6il.cjs.map +0 -1
  253. package/lib/node/getRawRequest-C2-1urzA.mjs +0 -24
  254. package/lib/node/getRawRequest-C2-1urzA.mjs.map +0 -1
  255. package/lib/node/handleRequest-C-cKYjIh.cjs +0 -222
  256. package/lib/node/handleRequest-C-cKYjIh.cjs.map +0 -1
  257. package/lib/node/handleRequest-FIQv5pwH.mjs +0 -192
  258. package/lib/node/handleRequest-FIQv5pwH.mjs.map +0 -1
  259. package/lib/node/hasConfigurableGlobal-BiTmog1u.mjs.map +0 -1
  260. package/lib/node/hasConfigurableGlobal-MjY06_Ok.cjs +0 -108
  261. package/lib/node/hasConfigurableGlobal-MjY06_Ok.cjs.map +0 -1
  262. package/lib/node/index-BJ5E5aW-.d.cts +0 -113
  263. package/lib/node/index-DfoF45ih.d.mts +0 -113
  264. package/lib/node/index.cjs +0 -53
  265. package/lib/node/index.cjs.map +0 -1
  266. package/lib/node/index.d.cts +0 -104
  267. package/lib/node/index.d.mts +0 -104
  268. package/lib/node/index.mjs.map +0 -1
  269. package/lib/node/interceptors/ClientRequest/index.cjs +0 -6
  270. package/lib/node/interceptors/ClientRequest/index.d.cts +0 -2
  271. package/lib/node/interceptors/ClientRequest/index.d.mts +0 -3
  272. package/lib/node/interceptors/ClientRequest/index.mjs +0 -6
  273. package/lib/node/interceptors/XMLHttpRequest/index.cjs +0 -7
  274. package/lib/node/interceptors/XMLHttpRequest/index.d.cts +0 -14
  275. package/lib/node/interceptors/XMLHttpRequest/index.d.mts +0 -14
  276. package/lib/node/interceptors/XMLHttpRequest/index.mjs +0 -7
  277. package/lib/node/interceptors/fetch/index.cjs +0 -6
  278. package/lib/node/interceptors/fetch/index.d.cts +0 -12
  279. package/lib/node/interceptors/fetch/index.d.mts +0 -12
  280. package/lib/node/interceptors/fetch/index.mjs +0 -6
  281. package/lib/node/node-DIKcnzhK.cjs +0 -39
  282. package/lib/node/node-DIKcnzhK.cjs.map +0 -1
  283. package/lib/node/node-lsdNwZEW.mjs +0 -27
  284. package/lib/node/node-lsdNwZEW.mjs.map +0 -1
  285. package/lib/node/presets/node.cjs +0 -23
  286. package/lib/node/presets/node.cjs.map +0 -1
  287. package/lib/node/presets/node.d.cts +0 -13
  288. package/lib/node/presets/node.d.mts +0 -15
  289. package/lib/node/presets/node.mjs +0 -23
  290. package/lib/node/presets/node.mjs.map +0 -1
  291. package/lib/node/utils/node/index.cjs +0 -4
  292. package/lib/node/utils/node/index.d.cts +0 -16
  293. package/lib/node/utils/node/index.d.mts +0 -16
  294. package/lib/node/utils/node/index.mjs +0 -3
  295. package/presets/browser/package.json +0 -5
  296. package/presets/node/package.json +0 -11
  297. package/src/BatchInterceptor.test.ts +0 -255
  298. package/src/BatchInterceptor.ts +0 -96
  299. package/src/Interceptor.test.ts +0 -205
  300. package/src/Interceptor.ts +0 -249
  301. package/src/getRawRequest.ts +0 -21
  302. package/src/interceptors/ClientRequest/MockHttpSocket.ts +0 -724
  303. package/src/interceptors/ClientRequest/agents.ts +0 -110
  304. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +0 -54
  305. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +0 -45
  306. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +0 -427
  307. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +0 -268
  308. package/src/interceptors/ClientRequest/utils/parserUtils.ts +0 -48
  309. package/src/interceptors/Socket/MockSocket.test.ts +0 -264
  310. package/src/interceptors/Socket/MockSocket.ts +0 -58
  311. package/src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts +0 -26
  312. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.test.ts +0 -52
  313. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts +0 -33
  314. package/src/interceptors/XMLHttpRequest/index.ts +0 -40
  315. package/src/interceptors/fetch/index.ts +0 -203
  316. package/src/utils/bufferUtils.ts +0 -22
  317. package/src/utils/canParseUrl.ts +0 -13
  318. package/src/utils/emitAsync.ts +0 -25
  319. package/src/utils/nextTick.ts +0 -11
  320. package/src/utils/node/index.ts +0 -39
  321. /package/src/{createRequestId.ts → create-request-id.ts} +0 -0
  322. /package/src/{InterceptorError.ts → interceptor-error.ts} +0 -0
  323. /package/src/interceptors/WebSocket/utils/{bindEvent.ts → bind-event.ts} +0 -0
  324. /package/src/interceptors/XMLHttpRequest/polyfills/{EventPolyfill.ts → event-polyfill.ts} +0 -0
  325. /package/src/interceptors/XMLHttpRequest/utils/{getBodyByteLength.ts → get-body-byte-length.ts} +0 -0
  326. /package/src/interceptors/XMLHttpRequest/utils/{isDomParserSupportedType.ts → is-dom-parser-supported-type.ts} +0 -0
  327. /package/src/interceptors/fetch/utils/{createNetworkError.ts → create-network-error.ts} +0 -0
  328. /package/src/utils/{findPropertySource.ts → find-property-source.ts} +0 -0
  329. /package/src/utils/{getCleanUrl.ts → get-clean-url.ts} +0 -0
  330. /package/src/utils/{getValueBySymbol.ts → get-value-by-symbol.ts} +0 -0
  331. /package/src/utils/{isNodeLikeError.ts → is-node-like-error.ts} +0 -0
  332. /package/src/utils/{isObject.ts → is-object.ts} +0 -0
  333. /package/src/utils/{isPropertyAccessible.ts → is-property-accessible.ts} +0 -0
  334. /package/src/utils/{parseJson.ts → parse-json.ts} +0 -0
  335. /package/src/utils/{resolveWebSocketUrl.ts → resolve-web-socket-url.ts} +0 -0
@@ -0,0 +1,688 @@
1
+ import net from 'node:net'
2
+ import {
3
+ METHODS,
4
+ STATUS_CODES,
5
+ ServerResponse,
6
+ IncomingMessage,
7
+ } from 'node:http'
8
+ import { invariant } from 'outvariant'
9
+ import { HttpResponseEvent, type HttpRequestEventMap } from '../../events/http'
10
+ import { RequestController } from '../../request-controller'
11
+ import {
12
+ getRawFetchHeaders,
13
+ recordRawFetchHeaders,
14
+ } from '../ClientRequest/utils/record-raw-headers'
15
+ import { SocketInterceptor } from '../net'
16
+ import { connectionOptionsToUrl } from '../net/utils/connection-options-to-url'
17
+ import { toBuffer } from '../../utils/buffer-utils'
18
+ import { createRequestId } from '../../create-request-id'
19
+ import { HttpRequestParser, HttpResponseParser } from './http-parser'
20
+ import { handleRequest, HandleRequestOptions } from '../../utils/handle-request'
21
+ import { isResponseError, kErrorResponse } from '../../utils/response-utils'
22
+ import { createLogger } from '../../utils/logger'
23
+ import {
24
+ kRawSocket,
25
+ SocketController,
26
+ type FlushPendingDataFunction,
27
+ } from '../net/socket-controller'
28
+ import { unwrapPendingData } from '../net/utils/flush-writes'
29
+ import { FetchResponse } from '../../utils/fetch-utils'
30
+ import { requestContext } from '../../request-context'
31
+ import { Interceptor } from '#/src/interceptor'
32
+
33
+ const httpLogger = createLogger('http-request')
34
+
35
+ /**
36
+ * Interceptor for HTTP requests in Node.js.
37
+ * Routes socket connections through an HTTP parser.
38
+ */
39
+ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
40
+ static symbol = Symbol.for('node-http-request-source')
41
+
42
+ protected predicate(): boolean {
43
+ return true
44
+ }
45
+
46
+ protected setup(): void {
47
+ const socketInterceptor = Interceptor.singleton(SocketInterceptor)
48
+ socketInterceptor.apply(this)
49
+ this.subscriptions.push(() => {
50
+ socketInterceptor.dispose(this)
51
+ })
52
+
53
+ /**
54
+ * @note Record the raw values provided to Headers set/append
55
+ * in order to support "IncomingMessage.prototype.rawHeaders".
56
+ * This is meant for the headers in mocked responses.
57
+ */
58
+ this.subscriptions.push(recordRawFetchHeaders())
59
+
60
+ const controller = new AbortController()
61
+ this.subscriptions.push(() => controller.abort())
62
+
63
+ socketInterceptor.on(
64
+ 'connection',
65
+ ({ connectionOptions, socket, controller: socketController }) => {
66
+ let isHttpConnection: boolean | undefined
67
+ let requestParser: HttpRequestParser | undefined
68
+ let tunnelUrl: URL | undefined
69
+ let abortPendingRequest: (() => void) | undefined
70
+
71
+ /**
72
+ * @note Capture the request context of the connection itself.
73
+ * The socket is created synchronously within the request async
74
+ * context (e.g. inside the patched `http.request()`), but the
75
+ * first data may reach the socket from a foreign context (e.g.
76
+ * a form-data stream piped into the request), where sampling
77
+ * the request context yields nothing.
78
+ */
79
+ const connectionRequestContext = requestContext.getStore()
80
+
81
+ /**
82
+ * @note The client destroys the socket synchronously (e.g. Undici
83
+ * on request abort) but the socket teardown events ("error",
84
+ * "close") are emitted asynchronously, after the consumer has
85
+ * already observed the rejected request promise. Hook into the
86
+ * destroy itself so the pending request is aborted before any
87
+ * of its listeners can resume.
88
+ */
89
+ const rawSocket = socketController[kRawSocket]
90
+ const realSocketDestroy = rawSocket._destroy.bind(rawSocket)
91
+ rawSocket._destroy = (error, callback) => {
92
+ abortPendingRequest?.()
93
+ realSocketDestroy(error, callback)
94
+ }
95
+
96
+ /**
97
+ * @note Only inspect the first sent packet to determine the protocol.
98
+ * A single socket cannot be used for different protocols.
99
+ */
100
+ socket.on('data', (chunk) => {
101
+ if (isHttpConnection === false) {
102
+ return
103
+ }
104
+
105
+ /**
106
+ * @note A mocked "CONNECT" request has established a tunnel.
107
+ * The data that follows belongs to a new exchange addressed to
108
+ * the tunnel target. The parser stopped at the tunnel boundary
109
+ * (HTTP upgrade semantics), so tear it down and detect the
110
+ * tunneled protocol anew, like on a fresh connection.
111
+ */
112
+ if (tunnelUrl && requestParser) {
113
+ requestParser.free()
114
+ requestParser = undefined
115
+ isHttpConnection = undefined
116
+ socketController.reset()
117
+ }
118
+
119
+ if (requestParser) {
120
+ requestParser.execute(toBuffer(chunk))
121
+ return
122
+ }
123
+
124
+ const httpMessage = chunk.toString()
125
+ const httpMethod = httpMessage.split(' ')[0] || ''
126
+
127
+ // Ignore non-HTTP packets sent via this socket.
128
+ if (!METHODS.includes(httpMethod.toUpperCase())) {
129
+ isHttpConnection = false
130
+ return
131
+ }
132
+
133
+ isHttpConnection = true
134
+
135
+ const baseUrl =
136
+ tunnelUrl ?? connectionOptionsToUrl(connectionOptions, socket)
137
+
138
+ httpLogger.verbose('handling http message %o', {
139
+ httpMessage,
140
+ httpMethod,
141
+ baseUrl,
142
+ })
143
+
144
+ // Get the request initiator from the async context, falling
145
+ // back to the context captured at the connection time, then
146
+ // to the underlying socket.
147
+ const requestContextValue =
148
+ requestContext.getStore() ?? connectionRequestContext
149
+ const initiator = requestContextValue?.initiator || socket
150
+
151
+ requestParser = new HttpRequestParser({
152
+ connectionOptions: {
153
+ method: httpMethod,
154
+ url: baseUrl,
155
+ },
156
+ onRequest: async (parsedRequest, requestAbortController) => {
157
+ const request =
158
+ requestContextValue?.transformRequest?.(parsedRequest) ??
159
+ parsedRequest
160
+
161
+ /**
162
+ * @note A subsequent request arriving on a kept-alive socket
163
+ * that has already been handled (passed through or mocked).
164
+ * Clients like Undici reuse sockets without emitting the
165
+ * "free" event, so reset the controller here, at the HTTP
166
+ * message boundary, to handle the new request from the
167
+ * pending state again.
168
+ */
169
+ if (socketController['readyState'] !== SocketController.PENDING) {
170
+ socketController.reset()
171
+ }
172
+
173
+ const requestId = createRequestId()
174
+ const requestLogger = requestContextValue?.logger ?? httpLogger
175
+
176
+ httpLogger.verbose('received a parsed HTTP request %o', {
177
+ method: request.method,
178
+ url: request.url,
179
+ })
180
+
181
+ const requestController = new RequestController(
182
+ request,
183
+ {
184
+ respondWith: async (rawResponse) => {
185
+ httpLogger.verbose('respondWith() %o', {
186
+ status: rawResponse.status,
187
+ statusText: rawResponse.statusText,
188
+ hasBody: rawResponse.body != null,
189
+ })
190
+
191
+ /**
192
+ * @note The client may destroy the socket (e.g. on request
193
+ * abort) moments before a response arrives. A destroyed
194
+ * socket cannot be claimed and has no one reading it.
195
+ */
196
+ if (socket.destroyed) {
197
+ return
198
+ }
199
+
200
+ socketController.claim()
201
+
202
+ const response = FetchResponse.from(rawResponse, {
203
+ url: request.url,
204
+ })
205
+
206
+ /**
207
+ * @note A successful mocked response to a "CONNECT"
208
+ * request establishes a tunnel to the requested authority
209
+ * (e.g. "127.0.0.1:80"). The exchange that follows on this
210
+ * socket is addressed to that authority, not to the proxy.
211
+ */
212
+ if (request.method === 'CONNECT' && response.ok) {
213
+ tunnelUrl = new URL(`http://${request.url}`)
214
+ }
215
+
216
+ /**
217
+ * @note Clone the response before "respondWith" because it will
218
+ * consume its body. This way, we can have a readable response copy
219
+ * for the "response" event below.
220
+ */
221
+ const responseClone = isResponseError(response)
222
+ ? null
223
+ : response.clone()
224
+
225
+ const respond = () => {
226
+ return this.respondWith({
227
+ socket: socketController[kRawSocket],
228
+ request: context.request,
229
+ response,
230
+ })
231
+ }
232
+
233
+ if (responseClone) {
234
+ await this.emitter.emitAsPromise(
235
+ new HttpResponseEvent({
236
+ initiator,
237
+ requestId,
238
+ request: context.request,
239
+ response: responseClone,
240
+ responseType: 'mock',
241
+ })
242
+ )
243
+ }
244
+
245
+ if (socket.connecting) {
246
+ // Send a mocked response once the socket connects, just like the real server would.
247
+ // This preserves the correct order of events (e.g. connect, then data).
248
+ socket.once('connect', respond)
249
+ } else {
250
+ /**
251
+ * @note Reused sockets stay connected between requests and will not
252
+ * emit "connect" anymore. If that's the case, respond immediately.
253
+ */
254
+ await respond()
255
+ }
256
+ },
257
+ errorWith: (reason) => {
258
+ if (reason instanceof Error) {
259
+ socket.destroy(reason)
260
+ }
261
+ },
262
+ passthrough: () => {
263
+ const realSocket = socketController.passthrough(
264
+ this.#modifyHttpHeaders(context.request)
265
+ )
266
+
267
+ if (this.emitter.listenerCount('response') > 0) {
268
+ httpLogger.verbose(
269
+ 'found "response" listener, corking socket reads'
270
+ )
271
+
272
+ /**
273
+ * Suspend the delivery of the original response to the client
274
+ * until the "response" event listeners settle. This guarantees
275
+ * that the request promise (e.g. `await fetch()`) does not
276
+ * resolve before the listeners are done. The real socket keeps
277
+ * emitting data for the response parser meanwhile.
278
+ */
279
+ socketController.corkReads()
280
+
281
+ const responseParser = new HttpResponseParser({
282
+ onResponse: async (response) => {
283
+ httpLogger.verbose(
284
+ 'HTTP response parser parsed: %d %s',
285
+ response.status,
286
+ response.statusText
287
+ )
288
+
289
+ if (isResponseError(response)) {
290
+ httpLogger.verbose(
291
+ 'response is an error response, uncorking socket reads...'
292
+ )
293
+
294
+ socketController.uncorkReads()
295
+ return
296
+ }
297
+
298
+ FetchResponse.setUrl(request.url, response)
299
+
300
+ try {
301
+ httpLogger.verbose('emitting "response" event')
302
+ await this.emitter.emitAsPromise(
303
+ new HttpResponseEvent({
304
+ initiator,
305
+ requestId,
306
+ request: context.request,
307
+ response,
308
+ responseType: 'original',
309
+ })
310
+ )
311
+ } finally {
312
+ httpLogger.verbose('uncorking socket reads')
313
+ socketController.uncorkReads()
314
+
315
+ /**
316
+ * @note Informational responses other than
317
+ * "101 Switching Protocols" are followed by a final
318
+ * response on the same connection. Keep gating that
319
+ * final response on the "response" event listeners.
320
+ */
321
+ if (
322
+ response.status < 200 &&
323
+ response.status !== 101
324
+ ) {
325
+ socketController.corkReads()
326
+ }
327
+ }
328
+ },
329
+ })
330
+
331
+ realSocket
332
+ .on('data', (chunk) => responseParser.execute(chunk))
333
+ .on('close', () => responseParser.free())
334
+ }
335
+ },
336
+ },
337
+ {
338
+ logger: requestLogger,
339
+ requestId,
340
+ }
341
+ )
342
+
343
+ invariant(
344
+ socketController['readyState'] === SocketController.PENDING,
345
+ 'CANNOT HANDLE ALREADY HANDLED REQUEST',
346
+ request.method,
347
+ request.url,
348
+ socketController['readyState']
349
+ )
350
+
351
+ /**
352
+ * @note Create a request resolution context.
353
+ * This is so modifications to the "request" in upstream interceptors
354
+ * are correctly picked up by the underlying HTTP interceptor.
355
+ */
356
+ const context: HandleRequestOptions = {
357
+ initiator,
358
+ requestId,
359
+ request,
360
+ controller: requestController,
361
+ emitter: this.emitter,
362
+ logger: requestLogger,
363
+ }
364
+
365
+ /**
366
+ * @note The client destroying the socket while the request
367
+ * is still pending means the request was aborted (e.g. via
368
+ * `AbortController`). Abort the parsed request so its
369
+ * handling settles and late interactions with the request
370
+ * controller become controlled errors.
371
+ */
372
+ abortPendingRequest = () => {
373
+ if (
374
+ requestController.readyState === RequestController.PENDING
375
+ ) {
376
+ requestAbortController.abort()
377
+ }
378
+ }
379
+
380
+ try {
381
+ await handleRequest(context)
382
+ } finally {
383
+ abortPendingRequest = undefined
384
+ }
385
+ },
386
+ })
387
+
388
+ // Forward the first frame to the parser.
389
+ requestParser.execute(toBuffer(chunk))
390
+ })
391
+
392
+ socket.on('close', () => requestParser?.free())
393
+ },
394
+ {
395
+ signal: controller.signal,
396
+ }
397
+ )
398
+ }
399
+
400
+ private async respondWith(args: {
401
+ socket: net.Socket
402
+ request: Request
403
+ response: Response
404
+ }): Promise<void> {
405
+ const { socket, request, response } = args
406
+
407
+ if (socket.destroyed) {
408
+ return
409
+ }
410
+
411
+ if (isResponseError(response)) {
412
+ /**
413
+ * @note Reference the error response on the socket error so the
414
+ * client-side interceptors (e.g. fetch) can surface it to the
415
+ * consumer as the reason behind the failed request. Keep the
416
+ * reference non-enumerable so the error remains observably
417
+ * identical for the clients that expose it as-is.
418
+ */
419
+ socket.destroy(
420
+ Object.defineProperty(new TypeError('Network error'), kErrorResponse, {
421
+ value: response,
422
+ enumerable: false,
423
+ })
424
+ )
425
+ return
426
+ }
427
+
428
+ invariant(
429
+ !socket.connecting,
430
+ 'Failed to mock a response for "%s %s": socket has not connected',
431
+ request.method,
432
+ request.url
433
+ )
434
+
435
+ /**
436
+ * Use native server response handling in Node.js.
437
+ * @see https://github.com/nodejs/node/blob/13eb80f3b718452213e0fc449702aefbbfe4110f/lib/_http_server.js#L202
438
+ */
439
+ const incomingMessage = new IncomingMessage(socket)
440
+
441
+ /**
442
+ * @note Describe the request method so the response body is
443
+ * handled appropriately (e.g. "HEAD" responses must not write
444
+ * a body). The HTTP version is deliberately left unset: with it,
445
+ * `ServerResponse` frames bodies of unknown length as chunked,
446
+ * polluting the mocked response headers with "Transfer-Encoding"
447
+ * the mock never specified.
448
+ */
449
+ incomingMessage.method = request.method
450
+
451
+ const serverResponse = new ServerResponse(incomingMessage)
452
+
453
+ const responseSocket = new net.Socket()
454
+
455
+ responseSocket._writeGeneric = (writev, data, encoding, callback) => {
456
+ unwrapPendingData(data, (chunk, encoding) => {
457
+ socket.push(toBuffer(chunk), encoding)
458
+ })
459
+ callback?.()
460
+ }
461
+
462
+ responseSocket._destroy = (
463
+ error: Error | null,
464
+ callback: (error: Error | null) => void
465
+ ) => {
466
+ /**
467
+ * Only destroy the socket on stream errors.
468
+ * On a clean end, the socket is already signaled via `socket.push(null)`
469
+ * in the main response flow. Destroying it here prematurely would prevent
470
+ * the client from processing the response (e.g. calling `response.destroy()`).
471
+ * @see https://github.com/mswjs/interceptors/issues/738
472
+ */
473
+ if (error) {
474
+ socket.destroy()
475
+ }
476
+
477
+ callback(null)
478
+ }
479
+
480
+ responseSocket.on('drain', () => serverResponse.emit('drain'))
481
+ serverResponse.assignSocket(responseSocket)
482
+
483
+ serverResponse.removeHeader('connection')
484
+ serverResponse.removeHeader('date')
485
+
486
+ const rawResponseHeaders = getRawFetchHeaders(response.headers)
487
+ serverResponse.writeHead(
488
+ response.status,
489
+ response.statusText || STATUS_CODES[response.status],
490
+ rawResponseHeaders
491
+ )
492
+
493
+ /**
494
+ * @note Override the socket's `_destroy` before writing the response body.
495
+ * The underlying TCP handle (from `socket.connect()`) makes `_destroy` async
496
+ * (`_handle.close()` callback), which delays the 'error' event. Since the real
497
+ * TCP connection is irrelevant for mocked responses, take the synchronous path
498
+ * so that user-initiated `response.destroy(error)` emits the error promptly.
499
+ * This must happen before `serverResponse.end()` because the HTTP parser may
500
+ * fire the 'response' event synchronously during `socket.push()`.
501
+ */
502
+ socket._destroy = function (
503
+ error: Error | null,
504
+ callback: (error: Error | null) => void
505
+ ) {
506
+ if (error) {
507
+ /**
508
+ * Emit the error event as a microtask instead of relying on the default
509
+ * `process.nextTick(emitErrorNT)` from `callback(error)`. This is necessary
510
+ * because `respondWith` runs inside a microtask (from `await reader.read()`).
511
+ * A resolved promise continuation (from toWebResponse) is queued as
512
+ * another microtask during the same phase. Since microtasks are drained before
513
+ * nextTick, the test's `await` would resolve before the error event fires.
514
+ * Using `queueMicrotask` ensures the error event is emitted within the current
515
+ * microtask phase, before other queued microtasks.
516
+ */
517
+ queueMicrotask(() => this.emit('error', error))
518
+ }
519
+
520
+ callback(null)
521
+
522
+ /**
523
+ * @note `net.Socket` is constructed with `emitClose: false`, so Node's
524
+ * stream destroy machinery does not emit `'close'` automatically; the
525
+ * stock `net.Socket._destroy` only emits it via `_handle.close()`.
526
+ * Since this override replaces `_destroy`, emit `'close'` here so the
527
+ * mocked socket completes its lifecycle (otherwise consumers waiting
528
+ * on `'close'`, like `http.ClientRequest`, hang).
529
+ */
530
+ process.nextTick(() => this.emit('close', error != null))
531
+ }
532
+
533
+ if (response.body) {
534
+ const reader = response.body.getReader()
535
+
536
+ try {
537
+ while (true) {
538
+ const { done, value } = await reader.read()
539
+
540
+ if (done) {
541
+ serverResponse.end()
542
+ break
543
+ }
544
+
545
+ if (!serverResponse.write(value)) {
546
+ await new Promise<void>((resolve) => {
547
+ serverResponse.once('drain', resolve)
548
+ })
549
+ }
550
+ }
551
+ } catch {
552
+ /**
553
+ * @note Delay the socket destruction to allow the event loop
554
+ * to flush already-pushed response data (headers + body chunks)
555
+ * through the HTTP parser. Without this, the socket is destroyed
556
+ * on the same tick as `socket.push(data)` and the client never
557
+ * reads the response.
558
+ */
559
+ await new Promise<void>((resolve) => process.nextTick(resolve))
560
+ socket.destroy()
561
+ return
562
+ }
563
+ } else {
564
+ serverResponse.end()
565
+ }
566
+
567
+ /**
568
+ * @note Self-delimiting responses (chunked, explicit "Content-Length",
569
+ * or bodiless by definition) must NOT signal the end-of-stream.
570
+ * The client parser completes them from their framing alone, and
571
+ * ending the socket would kill the kept-alive connection that
572
+ * agents pool and reuse for subsequent requests.
573
+ */
574
+ const isSelfDelimitingResponse =
575
+ request.method === 'HEAD' ||
576
+ response.headers.has('content-length') ||
577
+ response.headers.has('transfer-encoding') ||
578
+ !FetchResponse.isResponseWithBody(response.status)
579
+
580
+ if (request.method !== 'CONNECT' && !isSelfDelimitingResponse) {
581
+ /**
582
+ * @note Defer the end-of-stream signal so the HTTP parser has a chance
583
+ * to process already-pushed response data and fire the 'response' event
584
+ * before the socket is ended. Without this, the parser marks the response
585
+ * as "complete" before the client can interact with it (e.g. `response.destroy()`).
586
+ */
587
+ await new Promise<void>((resolve) => process.nextTick(resolve))
588
+ socket.push(null)
589
+ }
590
+ }
591
+
592
+ #modifyHttpHeaders(request: Request): FlushPendingDataFunction {
593
+ const transformRequestMessage = (
594
+ httpMessage: string | Buffer,
595
+ encoding?: BufferEncoding | 'buffer'
596
+ ): string | Buffer => {
597
+ /**
598
+ * @note Socket can write a buffer (e.g. uploaded file) even before
599
+ * it writes the HTTP message. Bypass those cases.
600
+ */
601
+ if (encoding === 'buffer') {
602
+ return httpMessage
603
+ }
604
+
605
+ const parts = httpMessage.toString(encoding).split('\r\n')
606
+ const headersEndIndex = parts.findIndex((field) => field === '')
607
+ const httpMessageHeaderPairs = parts.slice(1, headersEndIndex)
608
+
609
+ // Extract raw [name, value] tuples from the wire format so they
610
+ // can be compared against the request's raw fetch headers.
611
+ const httpMessageRawHeaders = httpMessageHeaderPairs.map(
612
+ (line): [string, string] => {
613
+ const separatorIndex = line.indexOf(': ')
614
+ return [line.slice(0, separatorIndex), line.slice(separatorIndex + 2)]
615
+ }
616
+ )
617
+
618
+ const requestRawHeaders = getRawFetchHeaders(request.headers)
619
+
620
+ // If the raw headers from the outgoing HTTP message and the request
621
+ // headers are identical, send the message as-is to avoid the cost
622
+ // (and side effects) of reserializing the headers block.
623
+ const headersUnchanged =
624
+ httpMessageRawHeaders.length === requestRawHeaders.length &&
625
+ httpMessageRawHeaders.every((tuple, index) => {
626
+ const requestTuple = requestRawHeaders[index]
627
+ return tuple[0] === requestTuple[0] && tuple[1] === requestTuple[1]
628
+ })
629
+
630
+ if (headersUnchanged) {
631
+ return httpMessage
632
+ }
633
+
634
+ const httpMessageHeaders = FetchResponse.parseRawHeaders(
635
+ httpMessageHeaderPairs.flatMap((header) => header.split(': '))
636
+ )
637
+
638
+ const visitedHeaders = new Set<string>()
639
+
640
+ for (const [headerName] of requestRawHeaders) {
641
+ const normalizedHeaderName = headerName.toLowerCase()
642
+
643
+ if (visitedHeaders.has(normalizedHeaderName)) {
644
+ continue
645
+ }
646
+
647
+ visitedHeaders.add(normalizedHeaderName)
648
+
649
+ /**
650
+ * @note Forbidden Fetch headers (e.g. Host, Origin, Connection)
651
+ * are stripped from `request.headers` but remain in the raw
652
+ * headers list. Skip them so the original values from the
653
+ * outgoing HTTP message are preserved.
654
+ */
655
+ const headerValue = request.headers.get(headerName)
656
+ if (headerValue === null) {
657
+ continue
658
+ }
659
+
660
+ // Use the merged value from Headers to correctly handle
661
+ // appended headers (e.g. "1, 2" instead of just "2").
662
+ httpMessageHeaders.set(headerName, headerValue)
663
+ }
664
+
665
+ visitedHeaders.clear()
666
+
667
+ const httpMessageHeadersString = Array.from(httpMessageHeaders)
668
+ .map(([name, value]) => `${name}: ${value}`)
669
+ .join('\r\n')
670
+ parts.splice(1, headersEndIndex - 1, httpMessageHeadersString)
671
+
672
+ return parts.join('\r\n')
673
+ }
674
+
675
+ return (pendingData, encoding, callback) => {
676
+ if (Array.isArray(pendingData)) {
677
+ pendingData[0].chunk = transformRequestMessage(
678
+ pendingData[0].chunk,
679
+ pendingData[0].encoding
680
+ )
681
+ } else {
682
+ pendingData = transformRequestMessage(pendingData, encoding)
683
+ }
684
+
685
+ callback(pendingData)
686
+ }
687
+ }
688
+ }