@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,1388 @@
1
+ import { a as createLogger, i as Interceptor, r as toBuffer } from "./buffer-utils-BvPY1Tc-.js";
2
+ import { TypedEvent } from "rettime";
3
+ import { invariant } from "outvariant";
4
+ import http from "node:http";
5
+ import net from "node:net";
6
+ import tls from "node:tls";
7
+ //#region src/utils/patches-registry.ts
8
+ var PatchesRegistry = class {
9
+ #replacements = /* @__PURE__ */ new Map();
10
+ applyPatch(owner, key, getNextValue) {
11
+ const ownerReplacements = this.#replacements.get(owner);
12
+ invariant(!ownerReplacements?.has(key), `Failed to replace a global value at "${String(key)}": already replaced.`);
13
+ const match = getDeepPropertyDescriptor(owner, key);
14
+ if (typeof match === "undefined") {
15
+ console.warn(`Failed to replace a global value at "${String(key)}": not a global value.`);
16
+ return () => {};
17
+ }
18
+ if (match.descriptor.configurable) Object.defineProperty(owner, key, {
19
+ value: getNextValue(owner[key]),
20
+ enumerable: true,
21
+ configurable: true
22
+ });
23
+ else if (match.descriptor.writable) owner[key] = getNextValue(owner[key]);
24
+ else throw new Error(`Failed to patch a non-configurable non-writable property "${key.toString()}"`);
25
+ const restorePatch = () => {
26
+ const currentReplacements = this.#replacements.get(owner);
27
+ if (!currentReplacements?.has(key)) return;
28
+ if (match.owner === owner)
29
+ /**
30
+ * @note Restoring non-configurable properties works as long as "writable: true"
31
+ * and none of the other descriptor properties except for "value" have changed.
32
+ */
33
+ Object.defineProperty(match.owner, key, match.descriptor);
34
+ else
35
+ /**
36
+ * @todo Delete the proxy property set by the registry.
37
+ * If the match's owner isn't the original owner, the property is likely nested in the prototype.
38
+ * The registry does not meddle with those, they are left intact.
39
+ */
40
+ Reflect.deleteProperty(owner, key);
41
+ currentReplacements.delete(key);
42
+ if (currentReplacements.size === 0) this.#replacements.delete(owner);
43
+ };
44
+ if (ownerReplacements) ownerReplacements.set(key, restorePatch);
45
+ else this.#replacements.set(owner, /* @__PURE__ */ new Map([[key, restorePatch]]));
46
+ return restorePatch;
47
+ }
48
+ restoreAllPatches() {
49
+ const errors = [];
50
+ for (const [, ownerReplacements] of this.#replacements) for (const [, restorePatch] of ownerReplacements) try {
51
+ restorePatch();
52
+ } catch (error) {
53
+ if (error instanceof Error) errors.push(error);
54
+ else throw error;
55
+ }
56
+ if (errors.length > 0) throw new AggregateError(errors, "FOO!");
57
+ }
58
+ };
59
+ const patchesRegistry = new PatchesRegistry();
60
+ /**
61
+ * Returns a property descriptor for the given property on the owner.
62
+ * Walks down the prototype chain if the property does not exist on the owner.
63
+ * Handy for getting a global property descriptor where `globalThis` is
64
+ * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).
65
+ */
66
+ function getDeepPropertyDescriptor(owner, key) {
67
+ let currentOwner = owner;
68
+ let descriptor;
69
+ while (currentOwner) {
70
+ descriptor = Object.getOwnPropertyDescriptor(currentOwner, key);
71
+ if (descriptor) return {
72
+ owner: currentOwner,
73
+ descriptor
74
+ };
75
+ currentOwner = Object.getPrototypeOf(currentOwner);
76
+ }
77
+ }
78
+ //#endregion
79
+ //#region src/interceptors/net/utils/normalize-net-connect-args.ts
80
+ /**
81
+ * Normalizes the arguments passed to `net.connect()`.
82
+ */
83
+ function normalizeNetConnectArgs(args) {
84
+ if (args.length === 0) return [{ path: "" }, null];
85
+ const callback = typeof args[1] === "function" ? args[1] : args[2] || null;
86
+ if (typeof args[0] === "string") return [{ path: args[0] }, callback];
87
+ if (typeof args[0] === "number") return [{
88
+ port: args[0],
89
+ path: "",
90
+ /**
91
+ * @note The host is optional in the "(port[, host][, callback])"
92
+ * signatures and defaults to "localhost" in Node.js.
93
+ */
94
+ host: typeof args[1] === "string" ? args[1] : void 0
95
+ }, callback];
96
+ if (typeof args[0] === "object") {
97
+ /**
98
+ * @note URL arguments receive no special treatment on purpose.
99
+ * Node.js does not support them and reads a given URL as a plain
100
+ * options object (e.g. "url.port" is a string, "url.host" includes
101
+ * the port). The "port" branch below reproduces that reading.
102
+ */
103
+ if ("port" in args[0]) return [{
104
+ path: "",
105
+ port: Reflect.get(args[0], "port"),
106
+ host: Reflect.get(args[0], "host"),
107
+ auth: Reflect.get(args[0], "auth"),
108
+ family: Reflect.get(args[0], "family"),
109
+ hints: Reflect.get(args[0], "hints"),
110
+ session: Reflect.get(args[0], "session"),
111
+ localAddress: Reflect.get(args[0], "localAddress"),
112
+ localPort: Reflect.get(args[0], "localPort"),
113
+ timeout: Reflect.get(args[0], "timeout"),
114
+ lookup: Reflect.get(args[0], "lookup"),
115
+ allowHalfOpen: Reflect.get(args[0], "allowHalfOpen"),
116
+ noDelay: Reflect.get(args[0], "noDelay"),
117
+ keepAlive: Reflect.get(args[0], "keepAlive"),
118
+ keepAliveInitialDelay: Reflect.get(args[0], "keepAliveInitialDelay"),
119
+ autoSelectFamily: Reflect.get(args[0], "autoSelectFamily"),
120
+ autoSelectFamilyAttemptTimeout: Reflect.get(args[0], "autoSelectFamilyAttemptTimeout")
121
+ }, callback];
122
+ return [{
123
+ path: args[0].path || "",
124
+ family: Reflect.get(args[0], "family"),
125
+ session: Reflect.get(args[0], "session"),
126
+ auth: Reflect.get(args[0], "auth"),
127
+ timeout: Reflect.get(args[0], "timeout"),
128
+ allowHalfOpen: Reflect.get(args[0], "allowHalfOpen")
129
+ }, callback];
130
+ }
131
+ throw new Error(`Invalid arguments passed to net.connect: ${args}`);
132
+ }
133
+ //#endregion
134
+ //#region src/interceptors/net/utils/flush-writes.ts
135
+ /**
136
+ * Write the given pending data to the socket using its public
137
+ * "write()" method. Unlike direct "_writeGeneric()" calls, public
138
+ * writes go through the "Writable" queue that only dispatches the
139
+ * next write once the previous one completes. TLS sockets rely on
140
+ * that serialization: their "TLSWrap" handle supports a single
141
+ * write in flight and crashes the process with a native
142
+ * "Assertion failed: !current_write_" abort when writes overlap
143
+ * (e.g. multiple direct "_writeGeneric()" calls issued on a
144
+ * connecting socket replay back-to-back on "connect").
145
+ */
146
+ function writePendingData(socket, data, encoding, callback) {
147
+ if (Array.isArray(data)) {
148
+ for (let index = 0; index < data.length; index++) {
149
+ const entry = data[index];
150
+ /**
151
+ * @note Per-entry callbacks are intentionally ignored, the same
152
+ * way "writevGeneric()" only honors the batch-level callback.
153
+ */
154
+ if (index === data.length - 1) socket.write(entry.chunk, entry.encoding, callback);
155
+ else socket.write(entry.chunk, entry.encoding);
156
+ }
157
+ return;
158
+ }
159
+ socket.write(data, encoding, callback);
160
+ }
161
+ function unwrapPendingData(data, callback) {
162
+ if (Array.isArray(data)) for (const entry of data) callback(entry.chunk, entry.encoding, entry.callback);
163
+ else callback(data);
164
+ }
165
+ //#endregion
166
+ //#region src/interceptors/net/utils/get-tls-connect-options.ts
167
+ /**
168
+ * Returns the original options the given TLS socket was created with.
169
+ * The original `tls.connect()` stores them on the socket instance
170
+ * under an internal symbol before connecting its transport.
171
+ * @see https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603f1/lib/_tls_wrap.js#L1690
172
+ */
173
+ function getTlsConnectOptions(socket) {
174
+ const kConnectOptions = Object.getOwnPropertySymbols(socket).find((symbol) => {
175
+ return symbol.description === "connect-options";
176
+ });
177
+ if (kConnectOptions == null) return;
178
+ return Reflect.get(socket, kConnectOptions);
179
+ }
180
+ //#endregion
181
+ //#region src/interceptors/net/utils/address-info.ts
182
+ function getAddressInfoByConnectionOptions(options) {
183
+ if (options == null) return {};
184
+ const isIPv6 = options.family === 6 || net.isIPv6(options.host || "");
185
+ return {
186
+ address: isIPv6 ? "::1" : "127.0.0.1",
187
+ /**
188
+ * @note Coerce the port to a number. Connection options may
189
+ * describe it as a string (e.g. when created from a URL), while
190
+ * the address info always reports a numeric port.
191
+ */
192
+ port: Number(options.port) || (options.protocol === "https:" ? 443 : 80),
193
+ family: isIPv6 ? "IPv6" : "IPv4"
194
+ };
195
+ }
196
+ /**
197
+ * Get the local address info for the given connection options.
198
+ * This describes the client-side end of the connection: the socket
199
+ * is bound to the loopback interface and an ephemeral port, the same
200
+ * way the operating system binds an outgoing connection.
201
+ */
202
+ function getLocalAddressInfoByConnectionOptions(options) {
203
+ if (options == null) return {};
204
+ const isIPv6 = options.family === 6 || net.isIPv6(options.host || "");
205
+ return {
206
+ address: options.localAddress || (isIPv6 ? "::1" : "127.0.0.1"),
207
+ port: options.localPort || getEphemeralPort(),
208
+ family: isIPv6 ? "IPv6" : "IPv4"
209
+ };
210
+ }
211
+ /**
212
+ * Get a random port from the ephemeral range (IANA: 49152-65535),
213
+ * the range the operating system draws from when binding an
214
+ * outgoing connection.
215
+ */
216
+ function getEphemeralPort() {
217
+ return 49152 + Math.floor(Math.random() * 16384);
218
+ }
219
+ //#endregion
220
+ //#region src/interceptors/net/socket-controller.ts
221
+ const kListenerWrap = Symbol("kListenerWrap");
222
+ const kRawSocket = Symbol("kRawSocket");
223
+ const kPatched = Symbol("kPatched");
224
+ const logger$1 = createLogger("socket");
225
+ function toServerSocket(socket) {
226
+ /**
227
+ * The server-side write buffer. Pushing data to the client honors
228
+ * the client's read backpressure: once "socket.push()" reports a
229
+ * full buffer, subsequent server writes queue here and flush when
230
+ * the client reads again ("_read"). This mirrors how a real server
231
+ * cannot write faster than the client consumes.
232
+ */
233
+ const pendingWrites = [];
234
+ let pendingEnd;
235
+ let isBackpressured = false;
236
+ let isFlushScheduled = false;
237
+ const flushPendingWrites = () => {
238
+ /**
239
+ * @note The mocked connection is not established yet. Flush once
240
+ * it is, the same way a real server cannot write to a client
241
+ * that has not connected.
242
+ */
243
+ if (socket.connecting) {
244
+ isBackpressured = true;
245
+ if (!isFlushScheduled) {
246
+ isFlushScheduled = true;
247
+ socket.once("ready", () => {
248
+ isFlushScheduled = false;
249
+ flushPendingWrites();
250
+ });
251
+ }
252
+ return false;
253
+ }
254
+ while (pendingWrites.length > 0) {
255
+ const nextWrite = pendingWrites.shift();
256
+ socket._unrefTimer();
257
+ const canPushMore = socket.push(toBuffer(nextWrite.chunk, nextWrite.encoding), nextWrite.encoding);
258
+ nextWrite.callback?.();
259
+ if (!canPushMore) {
260
+ isBackpressured = true;
261
+ return false;
262
+ }
263
+ }
264
+ const wasBackpressured = isBackpressured;
265
+ isBackpressured = false;
266
+ if (pendingEnd) {
267
+ const finalEnd = pendingEnd;
268
+ pendingEnd = void 0;
269
+ if (finalEnd.chunk != null) socket.push(toBuffer(finalEnd.chunk, finalEnd.encoding), finalEnd.encoding);
270
+ socket.push(null);
271
+ finalEnd.callback?.();
272
+ }
273
+ if (wasBackpressured) socket.emit("internal:drain");
274
+ return true;
275
+ };
276
+ /**
277
+ * @note "_read" is the client asking for more data. Flush the
278
+ * buffered server writes so the delivery resumes as the client
279
+ * reads, completing the backpressure loop.
280
+ */
281
+ const realRead = socket._read.bind(socket);
282
+ socket._read = (size) => {
283
+ realRead(size);
284
+ if (pendingWrites.length > 0 || pendingEnd != null || isBackpressured) flushPendingWrites();
285
+ };
286
+ return new Proxy(socket, { get: (target, property, receiver) => {
287
+ const getRealValue = () => {
288
+ return Reflect.get(target, property, receiver);
289
+ };
290
+ if (property === "on" || property === "addListener" || property === "once" || property === "prependListener" || property === "prependOnceListener") {
291
+ const realAddListener = getRealValue();
292
+ return (event, listener) => {
293
+ if (event === "data") {
294
+ const listenerWrap = (chunk, encoding) => {
295
+ listener(toBuffer(chunk, encoding));
296
+ };
297
+ Object.defineProperty(listener, kListenerWrap, {
298
+ enumerable: false,
299
+ writable: false,
300
+ value: listenerWrap
301
+ });
302
+ /**
303
+ * @note Subscribe using the same method (e.g. "once") so its
304
+ * listener semantics apply to the internal channel too.
305
+ */
306
+ Reflect.apply(realAddListener, target, ["internal:write", listenerWrap]);
307
+ return target;
308
+ }
309
+ /**
310
+ * @note The "drain" event on the server socket signals the
311
+ * flush of the server-side write buffer. It is routed through
312
+ * an internal channel so it does not clash with the "drain"
313
+ * event of the underlying client socket.
314
+ */
315
+ if (event === "drain") {
316
+ Reflect.apply(realAddListener, target, ["internal:drain", listener]);
317
+ return target;
318
+ }
319
+ return realAddListener.call(target, event, listener);
320
+ };
321
+ }
322
+ if (property === "off" || property === "removeListener") {
323
+ const realRemoveListener = getRealValue();
324
+ return (event, listener) => {
325
+ if (event === "data") {
326
+ const listenerWrap = listener[kListenerWrap];
327
+ if (listenerWrap) return realRemoveListener.call(target, "internal:write", listenerWrap);
328
+ }
329
+ if (event === "drain") return realRemoveListener.call(target, "internal:drain", listener);
330
+ return realRemoveListener.call(target, event, listener);
331
+ };
332
+ }
333
+ if (property === "write") return ((chunk, encoding, callback) => {
334
+ if (typeof encoding === "function") {
335
+ callback = encoding;
336
+ encoding = void 0;
337
+ }
338
+ pendingWrites.push({
339
+ chunk,
340
+ encoding,
341
+ callback
342
+ });
343
+ /**
344
+ * @note Do not push more data to a client that is not
345
+ * reading. The write stays buffered until the client reads
346
+ * again, and "drain" signals the flush.
347
+ */
348
+ if (isBackpressured) return false;
349
+ return flushPendingWrites();
350
+ });
351
+ if (property === "end") return ((...args) => {
352
+ const callback = args[args.length - 1];
353
+ /**
354
+ * @note The end-of-stream is delivered once the buffered
355
+ * writes flush so the client never observes the EOF before
356
+ * the data that preceded it.
357
+ */
358
+ pendingEnd = {
359
+ chunk: typeof args[0] === "function" ? void 0 : args[0],
360
+ encoding: typeof args[1] === "string" ? args[1] : void 0,
361
+ callback: typeof callback === "function" ? callback : void 0
362
+ };
363
+ flushPendingWrites();
364
+ /**
365
+ * @note Do not end the client socket's writable side.
366
+ * The server ending the connection only signals EOF to the
367
+ * client (the client may keep writing on half-open sockets).
368
+ */
369
+ return target;
370
+ });
371
+ return getRealValue();
372
+ } });
373
+ }
374
+ var SocketController = class SocketController {
375
+ static {
376
+ this.PENDING = 0;
377
+ }
378
+ static {
379
+ this.CLAIMED = 1;
380
+ }
381
+ static {
382
+ this.PASSTHROUGH = 2;
383
+ }
384
+ constructor(socket) {
385
+ this[kRawSocket] = socket;
386
+ socket[kPatched] = true;
387
+ this.readyState = SocketController.PENDING;
388
+ }
389
+ /**
390
+ * Claim this socket. Once claimed, the connection attempt succeeds
391
+ * regardless of the requested host and the interceptor becomes the
392
+ * mocked server for this connection.
393
+ */
394
+ claim() {
395
+ invariant(this.readyState === SocketController.PENDING, "Failed to claim a socket connection: already handled (%s)", this.readyState);
396
+ this.readyState = SocketController.CLAIMED;
397
+ }
398
+ /**
399
+ * Establish this socket connection as-is.
400
+ */
401
+ passthrough() {
402
+ invariant(this.readyState === SocketController.PENDING, "Failed to passthrough a socket connection: already handled (%s)", this.readyState);
403
+ this.readyState = SocketController.PASSTHROUGH;
404
+ }
405
+ };
406
+ var TcpSocketController = class extends SocketController {
407
+ #connectionOptions;
408
+ #realWriteGeneric;
409
+ #passthroughSocket = null;
410
+ #bufferedWrites = [];
411
+ #readsCorked = false;
412
+ #corkedReads = [];
413
+ #realHandleSwapped = false;
414
+ #clientCloseEmitted = false;
415
+ #clientEndPushed = false;
416
+ #connectEmulated = false;
417
+ constructor(socket, createConnection, connectionOptions) {
418
+ super(socket);
419
+ this.socket = socket;
420
+ this.createConnection = createConnection;
421
+ /**
422
+ * @note Plain TCP sockets capture the connection options from the
423
+ * "socket.connect()" proxy below. TLS sockets carry additional
424
+ * TLS-level options that never pass through "socket.connect()",
425
+ * so those are provided explicitly (see "TlsSocketController").
426
+ */
427
+ this.#connectionOptions = connectionOptions;
428
+ this.socket._read = () => {
429
+ /**
430
+ * @note Resume the passthrough socket when the consumer asks for
431
+ * more data. Node.js calls "_read()" once the consumer drains the
432
+ * read buffer (e.g. after "resume()"). The passthrough socket may
433
+ * have been paused when the consumer's buffer got full, and this
434
+ * is the only signal to continue reading.
435
+ */
436
+ this.#passthroughSocket?.resume();
437
+ };
438
+ this.#realWriteGeneric = this.socket._writeGeneric;
439
+ this.#bufferedWrites = [];
440
+ this.socket._writeGeneric = (...args) => {
441
+ this.#handleWrite(args);
442
+ };
443
+ this.socket.connect = new Proxy(this.socket.connect, { apply: (target, thisArg, args) => {
444
+ logger$1.verbose("socket.connect() %o", args);
445
+ this.#connectionOptions = args[0];
446
+ /**
447
+ * @note Do not bind the intercepted socket to the requested
448
+ * local address/port. Binding reserves the port for real, and
449
+ * the passthrough connection (created with the original options)
450
+ * would then bind the same port again, resulting in a conflict.
451
+ * The requested values are still reflected in the address info
452
+ * of a claimed socket (see "claim()").
453
+ */
454
+ if (args[0] != null && typeof args[0] === "object" && (args[0].localAddress != null || args[0].localPort != null)) args[0] = {
455
+ ...args[0],
456
+ localAddress: void 0,
457
+ localPort: void 0
458
+ };
459
+ return Reflect.apply(target, thisArg, args);
460
+ } });
461
+ /**
462
+ * @note A single socket can be reused for connections to the same host.
463
+ * When one connection ends, the Agent frees the socket, then uses it
464
+ * to write the next request's HTTP message immediately. Use the "free"
465
+ * event to transition the controller into the pending state so the
466
+ * next exchange is handled anew.
467
+ */
468
+ socket.on("free", () => {
469
+ logger$1.verbose("client socket freed!");
470
+ this.reset();
471
+ }).on("close", () => {
472
+ logger$1.verbose("client socket closed!");
473
+ this.#clientCloseEmitted = true;
474
+ /**
475
+ * @note Destroy the passthrough socket, if any. The client
476
+ * socket is done, and a passthrough connection left open would
477
+ * linger until the server closes it (or error unhandled if it
478
+ * is still connecting).
479
+ */
480
+ this.#passthroughSocket?.destroy();
481
+ this.#passthroughSocket = null;
482
+ this.#bufferedWrites = [];
483
+ this.#readsCorked = false;
484
+ this.#corkedReads = [];
485
+ });
486
+ this.serverSocket = toServerSocket(this.socket);
487
+ this.pendingConnection = Promise.withResolvers();
488
+ this.#reset();
489
+ }
490
+ /**
491
+ * Reset this controller to the pending state so the next exchange
492
+ * on this socket can be handled anew. This is meant for kept-alive
493
+ * sockets that are reused for multiple exchanges by clients that
494
+ * don't emit the "free" event on the socket (e.g. Undici).
495
+ */
496
+ reset() {
497
+ /**
498
+ * @note Only settled (claimed or passed-through) sockets need a reset.
499
+ * Resetting a pending socket again would discard the writes already
500
+ * buffered for the next exchange (e.g. the Agent "free" event firing
501
+ * after the parser has reset this controller at a message boundary).
502
+ */
503
+ if (this.readyState === SocketController.PENDING) return;
504
+ this.#reset();
505
+ }
506
+ #reset() {
507
+ logger$1.verbose("resetting the socket...");
508
+ this.readyState = SocketController.PENDING;
509
+ this.pendingConnection = Promise.withResolvers();
510
+ this.#bufferedWrites = [];
511
+ this.#connectEmulated = false;
512
+ this.socket._pendingData = null;
513
+ this.socket._pendingEncoding = "";
514
+ const wrapHandle = (handle) => {
515
+ this.pendingConnection.promise.then(() => {
516
+ logger$1.verbose("connection request resolved!", this.readyState);
517
+ process.nextTick(() => {
518
+ /**
519
+ * @note If by this point the socket hasn't been handled,
520
+ * is still connecting, doesn't have any writes buffered,
521
+ * and has a "connect" listener, assume it's the "write after connect"
522
+ * scenario (e.g. undici). In that case, auto-claim the socket to
523
+ * transition to the connected state appropriately to its handle.
524
+ */
525
+ if (this.readyState === SocketController.PENDING && this.socket.connecting && this.#bufferedWrites.length === 0 && this.socket.listenerCount("connect") > 0) {
526
+ logger$1.verbose("assume connect->write socket, calling \"connect\" listeners...");
527
+ this.emulateConnect();
528
+ }
529
+ });
530
+ });
531
+ /**
532
+ * Remove the "setTypeOfService" from the handle, if present (Node.js v24+).
533
+ * Removing it has no effect on the socket but prevents the "setTypeOfService EBADF" error.
534
+ * @see https://github.com/nodejs/node/blob/69a970f76814d40f55cf162d0cc3632fe8a7e599/lib/net.js#L661
535
+ * @see https://github.com/nodejs/undici/blob/bf684f7de01616708a33a5d1c092177622394442/lib/dispatcher/client-h1.js#L1136
536
+ */
537
+ if (handle.setTypeOfService) handle.setTypeOfService = void 0;
538
+ handle.connect = handle.connect6 = (request) => {
539
+ logger$1.verbose("handle.connect()");
540
+ this.pendingConnection.resolve([request, handle]);
541
+ };
542
+ logger$1.verbose("socket handle wrapped! waiting for connection request...");
543
+ };
544
+ if (this.socket._handle) wrapHandle(this.socket._handle);
545
+ else this.socket.prependOnceListener("connectionAttempt", () => {
546
+ wrapHandle(this.socket._handle);
547
+ });
548
+ }
549
+ /**
550
+ * Handle a write performed on the client socket. Installed once as
551
+ * "_writeGeneric", this is the single write path for every controller
552
+ * state, dispatching on "readyState".
553
+ */
554
+ #handleWrite(args) {
555
+ const data = args[1];
556
+ logger$1.verbose("socket write (state: %d) %o", this.readyState, args);
557
+ /**
558
+ * @note Buffer the write BEFORE pushing the data to the server socket.
559
+ * Handling the pushed data may transition this controller within the
560
+ * same call stack, and each transition settles the buffered entry:
561
+ * "passthrough()" flushes it to the real socket, "claim()" drops it,
562
+ * and a reset at an HTTP message boundary keeps it for the next exchange.
563
+ */
564
+ this.#bufferedWrites.push(args);
565
+ if (this.readyState === SocketController.PENDING) {
566
+ /**
567
+ * @note Reflect the buffered write in "_pendingData" so it counts
568
+ * toward "bytesWritten", the same way Node.js counts the pending
569
+ * data of a connecting socket. Flushing the buffered writes resets
570
+ * "_pendingData" (see `passthrough()`).
571
+ */
572
+ const pendingData = Array.isArray(this.socket._pendingData) ? this.socket._pendingData : [];
573
+ unwrapPendingData(data, (chunk, chunkEncoding) => {
574
+ pendingData.push({
575
+ chunk,
576
+ encoding: chunkEncoding
577
+ });
578
+ });
579
+ this.socket._pendingData = pendingData;
580
+ if (this.socket.listenerCount("internal:write") === 0) {
581
+ logger$1.verbose("no server data listeners, scheduling to the next tick...");
582
+ process.nextTick(() => {
583
+ this.#push(data);
584
+ });
585
+ } else this.#push(data);
586
+ } else this.#push(data);
587
+ /**
588
+ * Dispatch on the state observed AFTER the push since handling the
589
+ * pushed data may have transitioned this controller synchronously.
590
+ */
591
+ switch (this.readyState) {
592
+ case SocketController.PENDING:
593
+ /**
594
+ * @note The write either awaits the verdict on this exchange or,
595
+ * if the push reset this controller at a message boundary, opens
596
+ * the next exchange (the reset cleared the buffer; re-buffer it).
597
+ */
598
+ if (!this.#bufferedWrites.includes(args)) this.#bufferedWrites.push(args);
599
+ this.#acknowledgeWrite(args);
600
+ return;
601
+ case SocketController.CLAIMED:
602
+ /**
603
+ * @note Once claimed, there's nowhere else to write chunks to.
604
+ * The data was delivered to the server socket; complete the write
605
+ * so the socket's writable state settles (enabling "finish").
606
+ */
607
+ this.#removeBufferedWrite(args);
608
+ this.#acknowledgeWrite(args);
609
+ return;
610
+ case SocketController.PASSTHROUGH:
611
+ /**
612
+ * @note A synchronous "passthrough()" has already flushed this
613
+ * write (with its callback) to the real socket.
614
+ */
615
+ if (!this.#removeBufferedWrite(args)) return;
616
+ /**
617
+ * @note Until the handle swap, the client socket's own handle
618
+ * cannot carry any data (it never actually connects). Write
619
+ * directly to the passthrough socket instead. This also prevents
620
+ * the "connecting" write replay of `Socket.prototype._writeGeneric`
621
+ * from pushing the same data to the server socket twice.
622
+ */
623
+ if (!this.#realHandleSwapped && this.#passthroughSocket) {
624
+ writePendingData(this.#passthroughSocket, data, args[2], args[3]);
625
+ return;
626
+ }
627
+ this.#realWriteGeneric.apply(this.socket, args);
628
+ }
629
+ }
630
+ /**
631
+ * Complete the given write by invoking its callback. The callback is
632
+ * then removed from the write entry so flushing that entry later
633
+ * (e.g. on passthrough) does not invoke it twice.
634
+ */
635
+ #acknowledgeWrite(args) {
636
+ const callback = args[3];
637
+ if (typeof callback === "function") {
638
+ callback();
639
+ args[3] = void 0;
640
+ }
641
+ }
642
+ #removeBufferedWrite(args) {
643
+ const index = this.#bufferedWrites.indexOf(args);
644
+ if (index === -1) return false;
645
+ this.#bufferedWrites.splice(index, 1);
646
+ return true;
647
+ }
648
+ emulateConnect() {
649
+ this.#connectEmulated = true;
650
+ /**
651
+ * @note Reflect the connected state before notifying the listeners,
652
+ * the same way Node.js does before emitting "connect".
653
+ */
654
+ Reflect.set(this.socket, "connecting", false);
655
+ /**
656
+ * @note Invoke the raw listeners so the "once" wrappers get
657
+ * consumed. This prevents listeners like the connection callback
658
+ * from being invoked again when "connect" is emitted for real
659
+ * (e.g. once the claimed connection completes).
660
+ */
661
+ for (const listener of this.socket.rawListeners("connect")) listener.apply(this.socket);
662
+ }
663
+ /**
664
+ * Push the given data to the server socket.
665
+ * This has no effect on the public-facing socket and is used
666
+ * only for the interceptors to subscribe to "socket.on('data')"
667
+ * before the data is actually written anywhere.
668
+ */
669
+ #push = (data) => {
670
+ if (data == null) return;
671
+ logger$1.verbose("server push %o", data);
672
+ unwrapPendingData(data, (chunk, encoding) => {
673
+ logger$1.verbose("server emitting \"data\" %o", {
674
+ chunk,
675
+ encoding
676
+ });
677
+ this.socket.emit("internal:write", chunk, encoding);
678
+ });
679
+ };
680
+ #onRealSocketConnect = () => {
681
+ if (!this.#passthroughSocket) return;
682
+ /**
683
+ * @note The consumer may destroy the socket while the passthrough
684
+ * connection is still being established. The passthrough "connect"
685
+ * (I/O poll phase) can arrive before the client's "close" callback
686
+ * (close phase) within the same event loop iteration. A destroyed
687
+ * socket must not emit "connect"/"ready".
688
+ */
689
+ if (this.socket.destroyed) {
690
+ this.#passthroughSocket.destroy();
691
+ return;
692
+ }
693
+ const replacedHandle = this.socket._handle;
694
+ const wasUnrefed = replacedHandle != null && typeof replacedHandle.hasRef === "function" && !replacedHandle.hasRef();
695
+ this.socket._handle = this.#passthroughSocket._handle;
696
+ this.#realHandleSwapped = true;
697
+ /**
698
+ * @note Preserve the ref state across the handle swap. If the
699
+ * consumer unrefed the socket while it was connecting, the swapped
700
+ * handle must not hold the process alive either.
701
+ */
702
+ if (wasUnrefed) this.socket._handle.unref?.();
703
+ /**
704
+ * @note Close the replaced handle. Nothing references it past this
705
+ * point, and left open, it keeps the process alive indefinitely.
706
+ * For TLS sockets, the replaced handle is a TLSWrap; close its
707
+ * underlying transport too (closing the wrap alone does not close
708
+ * the TCP handle it sits on).
709
+ */
710
+ if (replacedHandle != null) {
711
+ replacedHandle.close();
712
+ replacedHandle._parent?.close();
713
+ }
714
+ Reflect.set(this.socket, "connecting", false);
715
+ /**
716
+ * @note Read the remote address info once so it gets cached on the
717
+ * socket. The passthrough socket controls the swapped handle and may
718
+ * close it at any point (e.g. once the server ends the connection),
719
+ * while Node.js keeps serving the cached info for sockets that
720
+ * have connected. Reading must happen after the socket is no longer
721
+ * connecting (the info of connecting sockets is never cached).
722
+ */
723
+ this.socket.remoteAddress;
724
+ this.socket.emit("connect");
725
+ this.socket.emit("ready");
726
+ };
727
+ #onRealSocketConnectionAttemptFailed = (address, port, family, error) => {
728
+ this.socket.emit("connectionAttemptFailed", address, port, family, error);
729
+ };
730
+ #onRealSocketConnectionAttemptTimeout = (address, port, family) => {
731
+ this.socket.emit("connectionAttemptTimeout", address, port, family);
732
+ };
733
+ #onRealSocketData = (data) => {
734
+ logger$1.verbose("real socket \"data\" event %o", data);
735
+ /**
736
+ * @note Receiving data is socket activity. Refresh the idle timer
737
+ * of the client socket the same way its own reads would
738
+ * ("socket.setTimeout()" must not fire during an active transfer).
739
+ * The data arrives on the real socket, which only refreshes the
740
+ * real socket's timer.
741
+ */
742
+ this.socket._unrefTimer();
743
+ if (this.#readsCorked) {
744
+ logger$1.verbose("reads are corked, buffering the data...");
745
+ this.#corkedReads.push({
746
+ type: "data",
747
+ chunk: data
748
+ });
749
+ return;
750
+ }
751
+ if (!this.socket.push(data)) {
752
+ logger$1.verbose("client socket forbade more pushes, pausing the passthrough socket...");
753
+ this.#passthroughSocket?.pause();
754
+ }
755
+ };
756
+ #onRealSocketError = (error) => {
757
+ logger$1.verbose("real socket \"error\" event %o", error);
758
+ if (this.socket.destroyed) {
759
+ logger$1.verbose("real socket errored but client socket already destroyed, skipping...");
760
+ return;
761
+ }
762
+ logger$1.verbose("real socket errored, forwarding %o", error);
763
+ this.socket.destroy(error);
764
+ if (this.#realHandleSwapped) process.nextTick(() => this.socket.emit("close", true));
765
+ };
766
+ #onRealSocketEnd = () => {
767
+ this.socket._unrefTimer();
768
+ if (this.#readsCorked) {
769
+ this.#corkedReads.push({ type: "end" });
770
+ return;
771
+ }
772
+ this.#clientEndPushed = true;
773
+ this.socket.push(null);
774
+ };
775
+ #onRealSocketClose = (hadError) => {
776
+ /**
777
+ * @note The connection is fully closed and the swapped handle
778
+ * cannot shut down anymore. Report a synchronous shutdown so the
779
+ * automatic "end()" of a half-closed socket ("allowHalfOpen: false")
780
+ * finishes without errors once the consumer reads the end-of-stream.
781
+ */
782
+ if (this.#realHandleSwapped && this.socket._handle) this.socket._handle.shutdown = () => 1;
783
+ if (this.#readsCorked) {
784
+ this.#corkedReads.push({
785
+ type: "close",
786
+ hadError
787
+ });
788
+ return;
789
+ }
790
+ if (this.#clientCloseEmitted) return;
791
+ if (this.socket.destroyed && !this.#realHandleSwapped) return;
792
+ this.#emitClientClose(hadError);
793
+ };
794
+ /**
795
+ * Emit the "close" event on the client socket, honoring the order
796
+ * of the socket teardown events.
797
+ * @note The client may be paused with the received data (and the
798
+ * end-of-stream) still buffered. Node.js never emits "close" before
799
+ * "end" on a gracefully closed connection: the teardown waits until
800
+ * the consumer reads the buffered data.
801
+ */
802
+ #emitClientClose(hadError) {
803
+ if (this.#clientEndPushed && !this.socket.readableEnded && !this.socket.destroyed) {
804
+ let closeDelivered = false;
805
+ const deliverClose = (hadErrorOverride) => {
806
+ if (closeDelivered) return;
807
+ closeDelivered = true;
808
+ process.nextTick(() => {
809
+ if (!this.#clientCloseEmitted) this.socket.emit("close", hadErrorOverride ?? hadError);
810
+ });
811
+ };
812
+ this.socket.once("end", () => {
813
+ deliverClose();
814
+ });
815
+ /**
816
+ * @note The consumer may also destroy the socket before reading
817
+ * the buffered data. Node.js still emits "close" for such
818
+ * sockets, but the destroy machinery of a socket with a swapped
819
+ * (already closed) handle never completes. Deliver "close" here.
820
+ */
821
+ const realDestroy = this.socket._destroy;
822
+ this.socket._destroy = (error, callback) => {
823
+ deliverClose(error != null);
824
+ return realDestroy.call(this.socket, error, callback);
825
+ };
826
+ return;
827
+ }
828
+ this.socket.emit("close", hadError);
829
+ }
830
+ #onMockSocketDrain = () => {
831
+ logger$1.verbose("client socket drained!");
832
+ this.#passthroughSocket?.resume();
833
+ };
834
+ /**
835
+ * Suspend forwarding of the passthrough socket events ("data", "end", "close")
836
+ * to the client socket. The events are buffered in order until `uncorkReads()`
837
+ * is called. This allows the consumer to delay the delivery of the original
838
+ * response to the client (e.g. until its own asynchronous logic settles).
839
+ *
840
+ * @note Pausing the client socket is not enough to delay the delivery.
841
+ * Consumers like Undici read the pushed data from a paused socket
842
+ * directly via `socket.read()`, which is unaffected by `socket.pause()`.
843
+ */
844
+ corkReads() {
845
+ this.#readsCorked = true;
846
+ }
847
+ /**
848
+ * Resume forwarding of the passthrough socket events to the client socket,
849
+ * replaying any events buffered while the reads were corked.
850
+ */
851
+ uncorkReads() {
852
+ if (!this.#readsCorked) return;
853
+ this.#readsCorked = false;
854
+ for (const corkedRead of this.#corkedReads.splice(0)) switch (corkedRead.type) {
855
+ case "data":
856
+ if (!this.socket.push(corkedRead.chunk)) {
857
+ logger$1.verbose("client socket forbade more pushes, pausing the passthrough socket...");
858
+ this.#passthroughSocket?.pause();
859
+ }
860
+ break;
861
+ case "end":
862
+ this.#clientEndPushed = true;
863
+ this.socket.push(null);
864
+ break;
865
+ case "close":
866
+ this.#emitClientClose(corkedRead.hadError);
867
+ break;
868
+ }
869
+ }
870
+ claim() {
871
+ super.claim();
872
+ /**
873
+ * @note The client may destroy the socket before the connection
874
+ * is claimed (e.g. abort a request in-flight). There is no
875
+ * connection to mock then, and the destroyed socket has no handle.
876
+ */
877
+ if (this.socket.destroyed) {
878
+ logger$1.verbose("socket already destroyed, skipping claim...");
879
+ return;
880
+ }
881
+ /**
882
+ * @note Skip already connected sockets (e.g. kept-alive sockets
883
+ * reused for the next exchange). Sockets with an emulated "connect"
884
+ * only appear connected and must still complete the mock connection.
885
+ */
886
+ if (!this.socket.connecting && !this.#connectEmulated) {
887
+ logger$1.verbose("socket already connected, skipping claim...");
888
+ return;
889
+ }
890
+ logger$1.verbose("-> claim!");
891
+ /**
892
+ * @note Reflect the local end of the claimed socket, the same way
893
+ * the operating system reports the bound address of an outgoing
894
+ * connection via "socket.address()"/"localAddress"/"localPort".
895
+ * Patching the handle also prevents Node.js from handling the
896
+ * "getsockname" errors of the never-connected raw handle.
897
+ * @see https://github.com/nodejs/node/blob/13eb80f3b718452213e0fc449702aefbbfe4110f/lib/net.js#L971
898
+ */
899
+ this.socket._handle.getsockname = (addressInfo) => {
900
+ Object.assign(addressInfo, getLocalAddressInfoByConnectionOptions(this.#connectionOptions));
901
+ return 0;
902
+ };
903
+ /**
904
+ * @note Reflect the connection target as the peer of the claimed
905
+ * socket, the same way a connected socket reports the server it
906
+ * connected to via "remoteAddress"/"remotePort"/"remoteFamily".
907
+ */
908
+ this.socket._handle.getpeername = (addressInfo) => {
909
+ Object.assign(addressInfo, getAddressInfoByConnectionOptions(this.#connectionOptions));
910
+ return 0;
911
+ };
912
+ this.#bufferedWrites = [];
913
+ this.socket._pendingData = null;
914
+ this.socket._pendingEncoding = "";
915
+ this.pendingConnection.promise.then(([request, handle]) => {
916
+ logger$1.verbose("connection request resolved, mocking the connection...");
917
+ /**
918
+ * @note "afterConnect" asserts that the socket is connecting.
919
+ * Restore the flag if the connect was emulated earlier (emulation
920
+ * flips it so its listeners observe a connected socket).
921
+ * "afterConnect" itself sets it back to false.
922
+ */
923
+ if (this.#connectEmulated) Reflect.set(this.socket, "connecting", true);
924
+ /**
925
+ * @see https://github.com/nodejs/node/blob/9cd6630870b776e96c5cf0ac68c31e2f46df3835/lib/net.js#L1142
926
+ */
927
+ request.oncomplete(0, handle, request, true, true);
928
+ });
929
+ }
930
+ passthrough(flushPendingData) {
931
+ super.passthrough();
932
+ logger$1.verbose("-> passthrough!");
933
+ const createRealSocket = () => {
934
+ const realSocket = this.createConnection();
935
+ realSocket[kPatched] = true;
936
+ if (this.socket.timeout != null) realSocket.setTimeout(this.socket.timeout);
937
+ return realSocket;
938
+ };
939
+ const realSocket = this.#passthroughSocket && !this.#passthroughSocket.destroyed ? this.#passthroughSocket : createRealSocket();
940
+ if (realSocket !== this.#passthroughSocket) this.#passthroughSocket = realSocket;
941
+ if (this.#bufferedWrites.length === 0) logger$1.verbose("passthrough with empty writes buffer (state: %d)", this.readyState);
942
+ /**
943
+ * Flush any writes during the pending phase to the passthrough socket.
944
+ * @note These are written directly on the passthrough socket to prevent
945
+ * them from being forwarded as "data" events on the server (already emitted).
946
+ */
947
+ for (let i = 0; i < this.#bufferedWrites.length; i++) {
948
+ const pendingWrite = this.#bufferedWrites[i];
949
+ if (i === 0 && typeof flushPendingData === "function") {
950
+ const data = pendingWrite[1];
951
+ const encoding = pendingWrite[2];
952
+ flushPendingData(data, encoding, (nextData) => {
953
+ pendingWrite[1] = nextData;
954
+ });
955
+ }
956
+ const [, data, encoding, callback] = pendingWrite;
957
+ writePendingData(realSocket, data, encoding, callback);
958
+ }
959
+ this.#bufferedWrites = [];
960
+ this.socket._pendingData = null;
961
+ this.socket._pendingEncoding = "";
962
+ this.socket.address = realSocket.address.bind(realSocket);
963
+ this.socket.removeListener("drain", this.#onMockSocketDrain);
964
+ this.socket.on("drain", this.#onMockSocketDrain);
965
+ realSocket.removeListener("connect", this.#onRealSocketConnect).removeListener("connectionAttemptFailed", this.#onRealSocketConnectionAttemptFailed).removeListener("connectionAttemptTimeout", this.#onRealSocketConnectionAttemptTimeout).removeListener("data", this.#onRealSocketData).removeListener("error", this.#onRealSocketError).removeListener("end", this.#onRealSocketEnd).removeListener("close", this.#onRealSocketClose);
966
+ realSocket.once("connect", this.#onRealSocketConnect).on("connectionAttemptFailed", this.#onRealSocketConnectionAttemptFailed).on("connectionAttemptTimeout", this.#onRealSocketConnectionAttemptTimeout).on("data", this.#onRealSocketData).on("error", this.#onRealSocketError).on("end", this.#onRealSocketEnd).on("close", this.#onRealSocketClose);
967
+ return realSocket;
968
+ }
969
+ };
970
+ var TlsSocketController = class extends TcpSocketController {
971
+ /**
972
+ * @note The TLS connection options must be provided explicitly.
973
+ * They cannot be captured from "socket.connect()" like for plain
974
+ * TCP sockets because "tls.connect()" fixes them at the TLS socket
975
+ * construction, before its transport ever connects.
976
+ */
977
+ #tlsConnectionOptions;
978
+ constructor(socket, createConnection, tlsConnectionOptions) {
979
+ super(socket, createConnection, tlsConnectionOptions);
980
+ this.socket = socket;
981
+ this.createConnection = createConnection;
982
+ this.#tlsConnectionOptions = tlsConnectionOptions;
983
+ socket.prependListener("secureConnect", () => {
984
+ /**
985
+ * @note Reflect the negotiated ALPN protocol from the handle that
986
+ * completed the handshake. The socket sets "alpnProtocol" only in
987
+ * its own "_finishInit", which never runs for passthrough
988
+ * connections (the handshake completes on the passthrough socket
989
+ * whose handle this socket inherits).
990
+ */
991
+ socket.alpnProtocol = socket._handle.getALPNNegotiatedProtocol();
992
+ });
993
+ }
994
+ emulateConnect() {
995
+ super.emulateConnect();
996
+ for (const listener of this.socket.rawListeners("secureConnect")) listener.apply(this.socket);
997
+ }
998
+ claim() {
999
+ /**
1000
+ * @note The client may destroy the socket before the connection
1001
+ * is claimed. Defer to the parent class, which transitions the
1002
+ * ready state and skips the mock connection of destroyed sockets.
1003
+ */
1004
+ if (this.socket.destroyed) {
1005
+ super.claim();
1006
+ return;
1007
+ }
1008
+ /**
1009
+ * @note Reflect that the mocked connection is not authorized.
1010
+ * There is no real peer certificate to verify. The identity check
1011
+ * itself is skipped for claimed connections (see the
1012
+ * "isSessionReused" mock below).
1013
+ */
1014
+ this.socket.prependOnceListener("secureConnect", () => {
1015
+ Reflect.set(this.socket, "authorized", false);
1016
+ Reflect.set(this.socket, "authorizationError", "MOCKED_CONNECTION_NOT_VERIFIED");
1017
+ });
1018
+ const handle = this.socket._handle;
1019
+ handle.start = () => void 0;
1020
+ /**
1021
+ * Mock this to prevent the "Error: Worker exited unexpectedly" error.
1022
+ * This will trigger when "secure" is emitted.
1023
+ * @see https://github.com/nodejs/node/blob/bdc8131fa78089b81b74dbff467365afb6536e6a/lib/internal/tls/wrap.js#L1648
1024
+ */
1025
+ handle.verifyError = () => void 0;
1026
+ handle.getSession = () => {
1027
+ return Buffer.from("mocked session");
1028
+ };
1029
+ /**
1030
+ * @note Skip the server identity check for this mocked connection.
1031
+ * Node.js runs "options.checkServerIdentity" in "onConnectSecure"
1032
+ * against the peer certificate, and a mocked connection has no
1033
+ * real peer certificate — the caller's (or the default) validation
1034
+ * would fail and destroy the socket. The check is overridden on the
1035
+ * socket's internal connect options since the emulated handshake
1036
+ * still completes through the regular "onConnectSecure" path.
1037
+ * @see https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603f1/lib/_tls_wrap.js#L1621
1038
+ */
1039
+ const realTlsConnectOptions = getTlsConnectOptions(this.socket);
1040
+ if (realTlsConnectOptions) realTlsConnectOptions.checkServerIdentity = () => {};
1041
+ handle.getCipher = () => {
1042
+ return {
1043
+ name: "TLS_AES_256_GCM_SHA384",
1044
+ standardName: "TLS_AES_256_GCM_SHA384",
1045
+ version: "TLSv1.3"
1046
+ };
1047
+ };
1048
+ /**
1049
+ * Mock this to prevent a segfault on Node.js 26+. The native
1050
+ * implementation reads the negotiated group ("SSL_get0_group_name")
1051
+ * of a handshake that never happened. Node.js itself calls this
1052
+ * in "onConnectSecure" to validate the "minDHSize" option.
1053
+ * Reflect the ephemeral key exchange matching the mocked cipher.
1054
+ * @see https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603f1/lib/_tls_wrap.js#L1636
1055
+ */
1056
+ handle.getEphemeralKeyInfo = () => {
1057
+ return {
1058
+ type: "ECDH",
1059
+ name: "X25519",
1060
+ size: 253
1061
+ };
1062
+ };
1063
+ const requestedAlpnProtocols = this.#tlsConnectionOptions?.ALPNProtocols;
1064
+ if (Array.isArray(requestedAlpnProtocols) && requestedAlpnProtocols.length > 0) {
1065
+ const [preferredProtocol] = requestedAlpnProtocols;
1066
+ /**
1067
+ * @note Reflect the client's preferred ALPN protocol as the
1068
+ * negotiated one. The mocked server accepts whatever the
1069
+ * client prefers.
1070
+ */
1071
+ handle.getALPNNegotiatedProtocol = () => {
1072
+ return typeof preferredProtocol === "string" ? preferredProtocol : false;
1073
+ };
1074
+ }
1075
+ this.socket.once("connect", () => {
1076
+ /**
1077
+ * @note A TLS 1.3 handshake derives five secrets, each reported
1078
+ * via a separate "keylog" event before the handshake completes.
1079
+ * Reflect them with mocked key material matching the mocked
1080
+ * cipher (SHA-384 secrets; the format is "LABEL <client random>
1081
+ * <secret>", each value hex-encoded).
1082
+ */
1083
+ const mockedClientRandom = "0".repeat(64);
1084
+ const mockedSecret = "0".repeat(96);
1085
+ for (const keylogLabel of [
1086
+ "SERVER_HANDSHAKE_TRAFFIC_SECRET",
1087
+ "EXPORTER_SECRET",
1088
+ "SERVER_TRAFFIC_SECRET_0",
1089
+ "CLIENT_HANDSHAKE_TRAFFIC_SECRET",
1090
+ "CLIENT_TRAFFIC_SECRET_0"
1091
+ ]) this.socket.emit("keylog", Buffer.from(`${keylogLabel} ${mockedClientRandom} ${mockedSecret}\n`));
1092
+ handle.onhandshakedone();
1093
+ /**
1094
+ * @note A TLS 1.3 server issues two session tickets by default,
1095
+ * each emitting a separate "session" event on the client.
1096
+ */
1097
+ handle.onnewsession(1, Buffer.from("mocked session"));
1098
+ handle.onnewsession(2, Buffer.from("mocked session"));
1099
+ });
1100
+ super.claim();
1101
+ }
1102
+ passthrough(flushPendingData) {
1103
+ const realSocket = super.passthrough(flushPendingData);
1104
+ /**
1105
+ * @note Remove the internal "connect" listener added by the TLS socket.
1106
+ * Normally, that listener manages the SSL handshake. But since we're in passthrough,
1107
+ * we delegate that to the real socket. Leaving the listener on the mock socket while
1108
+ * inheriting the real socket's handle will result in the handshake performed twice, which is a no-op.
1109
+ * @see https://github.com/nodejs/node/blob/abddfc921bf2af02a04a6a5d2bca8e2d91d80958/lib/internal/tls/wrap.js#L1105
1110
+ *
1111
+ * This prevents the following error:
1112
+ * # node (vitest 4)[8686]: static void node::crypto::TLSWrap::Start(const FunctionCallbackInfo<Value> &) at ../src/crypto/crypto_tls.cc:589
1113
+ # Assertion failed: !wrap->started_
1114
+ */
1115
+ for (const connectListener of this.socket.listeners("connect")) if (connectListener === this.socket._start || "listener" in connectListener && connectListener.listener === this.socket._start) this.socket.removeListener("connect", connectListener);
1116
+ realSocket.on("secure", () => {
1117
+ this.socket.emit("secure");
1118
+ }).on("session", (...args) => {
1119
+ this.socket.emit("session", ...args);
1120
+ }).on("keylog", (...args) => {
1121
+ this.socket.emit("keylog", ...args);
1122
+ }).on("OCSPResponse", (...args) => {
1123
+ this.socket.emit("OCSPResponse", ...args);
1124
+ });
1125
+ return realSocket;
1126
+ }
1127
+ };
1128
+ //#endregion
1129
+ //#region src/interceptors/net/utils/normalize-tls-connect-args.ts
1130
+ function normalizeTlsConnectArgs(args) {
1131
+ /**
1132
+ * @note Despite incorrect type definitions, "tls.connect()" has all the
1133
+ * options of "net.connect()" and then those specific to TLS connections,
1134
+ * like "session" or "socket".
1135
+ * @see https://github.com/nodejs/node/blob/bdc8131fa78089b81b74dbff467365afb6536e6a/lib/internal/tls/wrap.js#L1615
1136
+ */
1137
+ const netConnectArgs = normalizeNetConnectArgs(args);
1138
+ const options = netConnectArgs[0];
1139
+ const callback = netConnectArgs[1];
1140
+ if (args[0] !== null && typeof args[0] === "object") Object.assign(options, args[0]);
1141
+ else if (args[1] !== null && typeof args[1] === "object") Object.assign(options, args[1]);
1142
+ else if (args[2] !== null && typeof args[2] === "object") Object.assign(options, args[2]);
1143
+ return callback ? [options, callback] : [options];
1144
+ }
1145
+ //#endregion
1146
+ //#region src/interceptors/net/index.ts
1147
+ var SocketConnectionEvent = class extends TypedEvent {
1148
+ constructor(data) {
1149
+ super(...["connection", {}]);
1150
+ this.socket = data.socket;
1151
+ this.connectionOptions = data.connectionOptions;
1152
+ this.controller = data.controller;
1153
+ }
1154
+ };
1155
+ const logger = createLogger("socket");
1156
+ /**
1157
+ * A DNS lookup function for intercepted sockets. It always succeeds,
1158
+ * resolving any hostname to the loopback address. This ensures the
1159
+ * "lookup"/"connectionAttempt" socket events fire even for non-existent
1160
+ * hosts, and no real DNS resolution is performed. Passthrough
1161
+ * connections are created with the original options and use the real
1162
+ * (or the caller's custom) lookup instead.
1163
+ */
1164
+ const mockLookup = (hostname, dnsOptions, callback) => {
1165
+ const family = dnsOptions.family === 6 ? 6 : 4;
1166
+ const address = family === 6 ? "::1" : "127.0.0.1";
1167
+ /**
1168
+ * @note Call back asynchronously since DNS lookup is always
1169
+ * asynchronous in Node.js. Calling back synchronously emits
1170
+ * the "lookup"/"connectionAttempt" socket events before the
1171
+ * consumer gets a chance to add listeners for them.
1172
+ */
1173
+ process.nextTick(() => {
1174
+ /**
1175
+ * @note Honor the Node.js lookup contract: the callback receives
1176
+ * an array of addresses only when the "all" option is set
1177
+ * (e.g. during the family autoselection). Otherwise, it receives
1178
+ * a single address and its family. Node.js rejects an array in
1179
+ * the latter case with "ERR_INVALID_IP_ADDRESS".
1180
+ */
1181
+ if (dnsOptions.all) {
1182
+ callback(null, [{
1183
+ address,
1184
+ family
1185
+ }]);
1186
+ return;
1187
+ }
1188
+ callback(null, address, family);
1189
+ });
1190
+ };
1191
+ /**
1192
+ * Interceptor for `net.Socket` connections.
1193
+ */
1194
+ var SocketInterceptor = class extends Interceptor {
1195
+ static {
1196
+ this.symbol = Symbol.for("socket-interceptor");
1197
+ }
1198
+ predicate() {
1199
+ return true;
1200
+ }
1201
+ setup() {
1202
+ const interceptor = this;
1203
+ /**
1204
+ * @note A synchronous re-entrancy latch for creating passthrough
1205
+ * TLS connections. The original "tls.connect()" constructs a TLS
1206
+ * socket and calls "socket.connect()" on it synchronously, and that
1207
+ * call must reach Node.js as-is instead of being intercepted again.
1208
+ */
1209
+ let isCreatingPassthroughConnection = false;
1210
+ this.subscriptions.push(
1211
+ /**
1212
+ * @note Intercept connections at the "net.Socket.prototype.connect"
1213
+ * level instead of patching the "net.connect()" module function.
1214
+ * ESM consumers snapshot the module bindings at import time
1215
+ * ("import * as net from 'node:net'"), so reassigning "net.connect"
1216
+ * is invisible to them. Every client connection ends up calling
1217
+ * "Socket.prototype.connect" (including the one made by the original
1218
+ * "net.connect()"), and prototype mutations are visible regardless
1219
+ * of how the module was imported.
1220
+ */
1221
+ patchesRegistry.applyPatch(net.Socket.prototype, "connect", (realSocketConnect) => {
1222
+ return function connect(...args) {
1223
+ const socket = this;
1224
+ /**
1225
+ * @note Skip the sockets this interceptor already controls
1226
+ * (the mock connect call below, re-connects of an intercepted
1227
+ * socket, passthrough sockets) and the sockets created while
1228
+ * dialing a passthrough TLS connection. Their connects must
1229
+ * reach Node.js as-is.
1230
+ */
1231
+ if (socket[kPatched] || isCreatingPassthroughConnection) return realSocketConnect.apply(socket, args);
1232
+ logger.verbose("socket.connect() %o", args);
1233
+ /**
1234
+ * @note The original "net.connect()"/"tls.connect()" normalize
1235
+ * the arguments themselves and call this method with the
1236
+ * normalized array instead of the individual arguments.
1237
+ */
1238
+ const connectArgs = Array.isArray(args[0]) ? args[0] : args;
1239
+ const [transportConnectionOptions, connectionCallback] = normalizeNetConnectArgs(connectArgs);
1240
+ logger.verbose("connection options %o", {
1241
+ transportConnectionOptions,
1242
+ connectionCallback
1243
+ });
1244
+ let controller;
1245
+ let connectionOptions;
1246
+ if (socket instanceof tls.TLSSocket) {
1247
+ /**
1248
+ * @note TLS sockets arrive here from the original
1249
+ * "tls.connect()", which connects the transport of the TLS
1250
+ * socket it constructs ("connectionCallback" is its internal
1251
+ * "_start" handshake trigger). The original TLS connection
1252
+ * options are recovered from the socket instance since the
1253
+ * construction has already happened.
1254
+ */
1255
+ const realTlsConnectionOptions = getTlsConnectOptions(socket);
1256
+ const [tlsConnectionOptions] = normalizeTlsConnectArgs([{
1257
+ ...transportConnectionOptions,
1258
+ ...realTlsConnectionOptions
1259
+ }]);
1260
+ connectionOptions = tlsConnectionOptions;
1261
+ controller = new TlsSocketController(socket, () => {
1262
+ /**
1263
+ * @note Create the passthrough connection via the original
1264
+ * "tls.connect()" with the original connection options
1265
+ * (the real DNS lookup and the caller's certificate
1266
+ * validation included). The latch exempts the transport
1267
+ * connect of that connection from interception.
1268
+ */
1269
+ isCreatingPassthroughConnection = true;
1270
+ try {
1271
+ return tls.connect(realTlsConnectionOptions ?? tlsConnectionOptions);
1272
+ } finally {
1273
+ isCreatingPassthroughConnection = false;
1274
+ }
1275
+ }, tlsConnectionOptions);
1276
+ } else {
1277
+ /**
1278
+ * @note Create passthrough connections without the connection
1279
+ * callback. The callback is already registered as a "connect"
1280
+ * listener on the consumer's socket. Passing it to the
1281
+ * passthrough connection would invoke it twice.
1282
+ */
1283
+ const passthroughArgs = connectArgs.filter((arg) => {
1284
+ return typeof arg !== "function";
1285
+ });
1286
+ /**
1287
+ * @note Create the passthrough socket with the original
1288
+ * options, the same way "net.connect()" does. Connect it via
1289
+ * the unpatched method so the passthrough connection is not
1290
+ * intercepted again.
1291
+ */
1292
+ const socketOptions = connectArgs[0] !== null && typeof connectArgs[0] === "object" && !("href" in connectArgs[0]) ? connectArgs[0] : {};
1293
+ connectionOptions = transportConnectionOptions;
1294
+ controller = new TcpSocketController(socket, () => {
1295
+ const passthroughSocket = new net.Socket(socketOptions);
1296
+ Reflect.apply(realSocketConnect, passthroughSocket, passthroughArgs);
1297
+ return passthroughSocket;
1298
+ });
1299
+ }
1300
+ process.nextTick(() => {
1301
+ if (socket.destroyed) return;
1302
+ if (!interceptor.emitter.emit(new SocketConnectionEvent({
1303
+ socket: controller.serverSocket,
1304
+ controller,
1305
+ connectionOptions
1306
+ }))) {
1307
+ logger.verbose("no \"connection\" listeners found on the interceptor, passthrough...");
1308
+ controller.passthrough();
1309
+ return;
1310
+ }
1311
+ logger.verbose("emitted \"connection\" event!");
1312
+ });
1313
+ logger.verbose("connecting the socket...");
1314
+ /**
1315
+ * @note The requested local address/port are stripped from the
1316
+ * actual "socket.connect()" call by the controller to prevent
1317
+ * binding the intercepted socket (see the "connect" proxy).
1318
+ */
1319
+ const mockConnectionOptions = { ...transportConnectionOptions };
1320
+ mockConnectionOptions.lookup = mockLookup;
1321
+ try {
1322
+ /**
1323
+ * @note The normalized options are looser than the declared
1324
+ * "SocketConnectOpts" (e.g. the port may be a string when a
1325
+ * URL is passed). Node.js validates them at runtime.
1326
+ * This call goes through the controller's "connect" proxy
1327
+ * and lands back in this patch, where the "kPatched" check
1328
+ * above delegates it to the unpatched method.
1329
+ */
1330
+ return socket.connect(mockConnectionOptions, connectionCallback ?? void 0);
1331
+ } catch (error) {
1332
+ /**
1333
+ * @note "socket.connect()" can throw synchronously on invalid
1334
+ * input (e.g. a bad port). Destroy the socket so the pending
1335
+ * interception tick does not act on it, then let the error
1336
+ * propagate to the consumer like in Node.js.
1337
+ */
1338
+ socket.destroy();
1339
+ throw error;
1340
+ }
1341
+ };
1342
+ }),
1343
+ this.#stopReusingUnpatchedSockets()
1344
+ );
1345
+ /**
1346
+ * @note "net.connect()"/"net.createConnection()" need no patching of
1347
+ * their own: both construct a "net.Socket" and call the patched
1348
+ * "Socket.prototype.connect" on it.
1349
+ */
1350
+ }
1351
+ /**
1352
+ * Prevent the `net.Socket` instances created before this interceptor
1353
+ * was applied from getting reused by an `Agent`. Purging them from the
1354
+ * keep-alive pool forces the agent to establish new (intercepted)
1355
+ * connections instead.
1356
+ */
1357
+ #stopReusingUnpatchedSockets() {
1358
+ /**
1359
+ * @note "Agent.prototype.addRequest" is undocumented but stable:
1360
+ * its signature changed once (the keep-alive Agent rewrite in
1361
+ * Node.js 0.12), and the ecosystem (agent-base, agentkeepalive,
1362
+ * APM wrappers) has relied on it ever since. "https.Agent"
1363
+ * inherits it, so a single patch covers both.
1364
+ */
1365
+ if (typeof http.Agent.prototype.addRequest !== "function") return () => {};
1366
+ return patchesRegistry.applyPatch(http.Agent.prototype, "addRequest", (realAddRequest) => {
1367
+ return function(...args) {
1368
+ /**
1369
+ * @note Destroy the free sockets created before the interceptor
1370
+ * was applied. Destroying flips their "destroyed" state
1371
+ * synchronously, so the original "addRequest" below discards
1372
+ * them and dials a new (intercepted) connection instead.
1373
+ * The "freeSockets" pool only contains idle sockets by
1374
+ * definition, so destroying them aborts nothing in-flight.
1375
+ */
1376
+ for (const sockets of Object.values(this.freeSockets)) {
1377
+ if (sockets == null) continue;
1378
+ for (const socket of sockets) if (!socket[kPatched]) socket.destroy();
1379
+ }
1380
+ return realAddRequest?.apply(this, args);
1381
+ };
1382
+ });
1383
+ }
1384
+ };
1385
+ //#endregion
1386
+ export { getDeepPropertyDescriptor as a, unwrapPendingData as i, SocketController as n, patchesRegistry as o, kRawSocket as r, SocketInterceptor as t };
1387
+
1388
+ //# sourceMappingURL=net-DtMnyEeg.js.map