@depup/mswjs__interceptors 0.41.3-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/ClientRequest/package.json +11 -0
  2. package/LICENSE.md +9 -0
  3. package/README.md +31 -0
  4. package/RemoteHttpInterceptor/package.json +11 -0
  5. package/WebSocket/package.json +12 -0
  6. package/XMLHttpRequest/package.json +12 -0
  7. package/changes.json +10 -0
  8. package/fetch/package.json +12 -0
  9. package/lib/browser/Interceptor-Deczogc8.d.cts +65 -0
  10. package/lib/browser/Interceptor-gqKgs-aF.d.mts +65 -0
  11. package/lib/browser/XMLHttpRequest-BACqefB-.cjs +761 -0
  12. package/lib/browser/XMLHttpRequest-BACqefB-.cjs.map +1 -0
  13. package/lib/browser/XMLHttpRequest-BvxZV0WU.mjs +756 -0
  14. package/lib/browser/XMLHttpRequest-BvxZV0WU.mjs.map +1 -0
  15. package/lib/browser/bufferUtils-BiiO6HZv.mjs +20 -0
  16. package/lib/browser/bufferUtils-BiiO6HZv.mjs.map +1 -0
  17. package/lib/browser/bufferUtils-Uc0eRItL.cjs +38 -0
  18. package/lib/browser/bufferUtils-Uc0eRItL.cjs.map +1 -0
  19. package/lib/browser/createRequestId-Cs4oXfa1.cjs +205 -0
  20. package/lib/browser/createRequestId-Cs4oXfa1.cjs.map +1 -0
  21. package/lib/browser/createRequestId-DQcIlohW.mjs +170 -0
  22. package/lib/browser/createRequestId-DQcIlohW.mjs.map +1 -0
  23. package/lib/browser/fetch-DdKEdDOR.mjs +248 -0
  24. package/lib/browser/fetch-DdKEdDOR.mjs.map +1 -0
  25. package/lib/browser/fetch-U3v3Y4ap.cjs +253 -0
  26. package/lib/browser/fetch-U3v3Y4ap.cjs.map +1 -0
  27. package/lib/browser/getRawRequest-BTaNLFr0.mjs +218 -0
  28. package/lib/browser/getRawRequest-BTaNLFr0.mjs.map +1 -0
  29. package/lib/browser/getRawRequest-zx8rUJL2.cjs +259 -0
  30. package/lib/browser/getRawRequest-zx8rUJL2.cjs.map +1 -0
  31. package/lib/browser/glossary-BdLS4k1H.d.cts +70 -0
  32. package/lib/browser/glossary-DYwOrogs.d.mts +70 -0
  33. package/lib/browser/handleRequest-CvX2G-Lz.cjs +189 -0
  34. package/lib/browser/handleRequest-CvX2G-Lz.cjs.map +1 -0
  35. package/lib/browser/handleRequest-D7kpTI5U.mjs +178 -0
  36. package/lib/browser/handleRequest-D7kpTI5U.mjs.map +1 -0
  37. package/lib/browser/hasConfigurableGlobal-BvCTG97d.cjs +45 -0
  38. package/lib/browser/hasConfigurableGlobal-BvCTG97d.cjs.map +1 -0
  39. package/lib/browser/hasConfigurableGlobal-npXitu1-.mjs +33 -0
  40. package/lib/browser/hasConfigurableGlobal-npXitu1-.mjs.map +1 -0
  41. package/lib/browser/index.cjs +70 -0
  42. package/lib/browser/index.cjs.map +1 -0
  43. package/lib/browser/index.d.cts +96 -0
  44. package/lib/browser/index.d.mts +96 -0
  45. package/lib/browser/index.mjs +56 -0
  46. package/lib/browser/index.mjs.map +1 -0
  47. package/lib/browser/interceptors/WebSocket/index.cjs +622 -0
  48. package/lib/browser/interceptors/WebSocket/index.cjs.map +1 -0
  49. package/lib/browser/interceptors/WebSocket/index.d.cts +277 -0
  50. package/lib/browser/interceptors/WebSocket/index.d.mts +277 -0
  51. package/lib/browser/interceptors/WebSocket/index.mjs +615 -0
  52. package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -0
  53. package/lib/browser/interceptors/XMLHttpRequest/index.cjs +7 -0
  54. package/lib/browser/interceptors/XMLHttpRequest/index.d.cts +15 -0
  55. package/lib/browser/interceptors/XMLHttpRequest/index.d.mts +15 -0
  56. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +7 -0
  57. package/lib/browser/interceptors/fetch/index.cjs +6 -0
  58. package/lib/browser/interceptors/fetch/index.d.cts +13 -0
  59. package/lib/browser/interceptors/fetch/index.d.mts +13 -0
  60. package/lib/browser/interceptors/fetch/index.mjs +6 -0
  61. package/lib/browser/presets/browser.cjs +17 -0
  62. package/lib/browser/presets/browser.cjs.map +1 -0
  63. package/lib/browser/presets/browser.d.cts +12 -0
  64. package/lib/browser/presets/browser.d.mts +14 -0
  65. package/lib/browser/presets/browser.mjs +17 -0
  66. package/lib/browser/presets/browser.mjs.map +1 -0
  67. package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs +31 -0
  68. package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs.map +1 -0
  69. package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs +25 -0
  70. package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs.map +1 -0
  71. package/lib/node/BatchInterceptor-3LnAnLTx.cjs +49 -0
  72. package/lib/node/BatchInterceptor-3LnAnLTx.cjs.map +1 -0
  73. package/lib/node/BatchInterceptor-D7mXzHcQ.d.mts +26 -0
  74. package/lib/node/BatchInterceptor-DFaBPilf.mjs +44 -0
  75. package/lib/node/BatchInterceptor-DFaBPilf.mjs.map +1 -0
  76. package/lib/node/BatchInterceptor-D_YqR8qU.d.cts +26 -0
  77. package/lib/node/ClientRequest-2rDe54Ui.cjs +1043 -0
  78. package/lib/node/ClientRequest-2rDe54Ui.cjs.map +1 -0
  79. package/lib/node/ClientRequest-Ca8Qykuv.mjs +1034 -0
  80. package/lib/node/ClientRequest-Ca8Qykuv.mjs.map +1 -0
  81. package/lib/node/Interceptor-DEazpLJd.d.mts +133 -0
  82. package/lib/node/Interceptor-DJ2akVWI.d.cts +133 -0
  83. package/lib/node/RemoteHttpInterceptor.cjs +154 -0
  84. package/lib/node/RemoteHttpInterceptor.cjs.map +1 -0
  85. package/lib/node/RemoteHttpInterceptor.d.cts +39 -0
  86. package/lib/node/RemoteHttpInterceptor.d.mts +39 -0
  87. package/lib/node/RemoteHttpInterceptor.mjs +152 -0
  88. package/lib/node/RemoteHttpInterceptor.mjs.map +1 -0
  89. package/lib/node/XMLHttpRequest-B7kJdYYI.cjs +763 -0
  90. package/lib/node/XMLHttpRequest-B7kJdYYI.cjs.map +1 -0
  91. package/lib/node/XMLHttpRequest-C8dIZpds.mjs +757 -0
  92. package/lib/node/XMLHttpRequest-C8dIZpds.mjs.map +1 -0
  93. package/lib/node/bufferUtils-DiCTqG-7.cjs +38 -0
  94. package/lib/node/bufferUtils-DiCTqG-7.cjs.map +1 -0
  95. package/lib/node/bufferUtils-_8XfKIfX.mjs +20 -0
  96. package/lib/node/bufferUtils-_8XfKIfX.mjs.map +1 -0
  97. package/lib/node/chunk-CbDLau6x.cjs +34 -0
  98. package/lib/node/fetch-BmXpK10r.cjs +272 -0
  99. package/lib/node/fetch-BmXpK10r.cjs.map +1 -0
  100. package/lib/node/fetch-G1DVwDKG.mjs +265 -0
  101. package/lib/node/fetch-G1DVwDKG.mjs.map +1 -0
  102. package/lib/node/fetchUtils-BaY5iWXw.cjs +419 -0
  103. package/lib/node/fetchUtils-BaY5iWXw.cjs.map +1 -0
  104. package/lib/node/fetchUtils-CoU35g3M.mjs +359 -0
  105. package/lib/node/fetchUtils-CoU35g3M.mjs.map +1 -0
  106. package/lib/node/getRawRequest-BavnMWh_.cjs +36 -0
  107. package/lib/node/getRawRequest-BavnMWh_.cjs.map +1 -0
  108. package/lib/node/getRawRequest-DnwmXyOW.mjs +24 -0
  109. package/lib/node/getRawRequest-DnwmXyOW.mjs.map +1 -0
  110. package/lib/node/glossary-BLKRyLBd.cjs +12 -0
  111. package/lib/node/glossary-BLKRyLBd.cjs.map +1 -0
  112. package/lib/node/glossary-glQBRnVD.mjs +6 -0
  113. package/lib/node/glossary-glQBRnVD.mjs.map +1 -0
  114. package/lib/node/handleRequest-Bb7Y-XLw.cjs +220 -0
  115. package/lib/node/handleRequest-Bb7Y-XLw.cjs.map +1 -0
  116. package/lib/node/handleRequest-Y97UwBbF.mjs +190 -0
  117. package/lib/node/handleRequest-Y97UwBbF.mjs.map +1 -0
  118. package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs +26 -0
  119. package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs.map +1 -0
  120. package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs +20 -0
  121. package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs.map +1 -0
  122. package/lib/node/index-BMbJ8FXL.d.cts +113 -0
  123. package/lib/node/index-C0YAQ36w.d.mts +113 -0
  124. package/lib/node/index.cjs +54 -0
  125. package/lib/node/index.cjs.map +1 -0
  126. package/lib/node/index.d.cts +75 -0
  127. package/lib/node/index.d.mts +75 -0
  128. package/lib/node/index.mjs +40 -0
  129. package/lib/node/index.mjs.map +1 -0
  130. package/lib/node/interceptors/ClientRequest/index.cjs +6 -0
  131. package/lib/node/interceptors/ClientRequest/index.d.cts +2 -0
  132. package/lib/node/interceptors/ClientRequest/index.d.mts +3 -0
  133. package/lib/node/interceptors/ClientRequest/index.mjs +6 -0
  134. package/lib/node/interceptors/XMLHttpRequest/index.cjs +6 -0
  135. package/lib/node/interceptors/XMLHttpRequest/index.d.cts +14 -0
  136. package/lib/node/interceptors/XMLHttpRequest/index.d.mts +14 -0
  137. package/lib/node/interceptors/XMLHttpRequest/index.mjs +6 -0
  138. package/lib/node/interceptors/fetch/index.cjs +5 -0
  139. package/lib/node/interceptors/fetch/index.d.cts +12 -0
  140. package/lib/node/interceptors/fetch/index.d.mts +12 -0
  141. package/lib/node/interceptors/fetch/index.mjs +5 -0
  142. package/lib/node/node-DwCc6iuP.mjs +27 -0
  143. package/lib/node/node-DwCc6iuP.mjs.map +1 -0
  144. package/lib/node/node-dKdAf3tC.cjs +39 -0
  145. package/lib/node/node-dKdAf3tC.cjs.map +1 -0
  146. package/lib/node/presets/node.cjs +22 -0
  147. package/lib/node/presets/node.cjs.map +1 -0
  148. package/lib/node/presets/node.d.cts +13 -0
  149. package/lib/node/presets/node.d.mts +15 -0
  150. package/lib/node/presets/node.mjs +22 -0
  151. package/lib/node/presets/node.mjs.map +1 -0
  152. package/lib/node/utils/node/index.cjs +4 -0
  153. package/lib/node/utils/node/index.d.cts +16 -0
  154. package/lib/node/utils/node/index.d.mts +16 -0
  155. package/lib/node/utils/node/index.mjs +3 -0
  156. package/package.json +204 -0
  157. package/presets/browser/package.json +5 -0
  158. package/presets/node/package.json +11 -0
  159. package/src/BatchInterceptor.test.ts +255 -0
  160. package/src/BatchInterceptor.ts +95 -0
  161. package/src/Interceptor.test.ts +205 -0
  162. package/src/Interceptor.ts +249 -0
  163. package/src/InterceptorError.ts +7 -0
  164. package/src/RemoteHttpInterceptor.ts +251 -0
  165. package/src/RequestController.test.ts +104 -0
  166. package/src/RequestController.ts +109 -0
  167. package/src/createRequestId.test.ts +7 -0
  168. package/src/createRequestId.ts +9 -0
  169. package/src/getRawRequest.ts +21 -0
  170. package/src/glossary.ts +37 -0
  171. package/src/index.ts +15 -0
  172. package/src/interceptors/ClientRequest/MockHttpSocket.ts +724 -0
  173. package/src/interceptors/ClientRequest/agents.ts +110 -0
  174. package/src/interceptors/ClientRequest/index.test.ts +75 -0
  175. package/src/interceptors/ClientRequest/index.ts +193 -0
  176. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +54 -0
  177. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +45 -0
  178. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +427 -0
  179. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +268 -0
  180. package/src/interceptors/ClientRequest/utils/parserUtils.ts +48 -0
  181. package/src/interceptors/ClientRequest/utils/recordRawHeaders.test.ts +258 -0
  182. package/src/interceptors/ClientRequest/utils/recordRawHeaders.ts +262 -0
  183. package/src/interceptors/Socket/MockSocket.test.ts +264 -0
  184. package/src/interceptors/Socket/MockSocket.ts +58 -0
  185. package/src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts +26 -0
  186. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.test.ts +52 -0
  187. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts +33 -0
  188. package/src/interceptors/WebSocket/WebSocketClassTransport.ts +116 -0
  189. package/src/interceptors/WebSocket/WebSocketClientConnection.ts +152 -0
  190. package/src/interceptors/WebSocket/WebSocketOverride.ts +252 -0
  191. package/src/interceptors/WebSocket/WebSocketServerConnection.ts +420 -0
  192. package/src/interceptors/WebSocket/WebSocketTransport.ts +39 -0
  193. package/src/interceptors/WebSocket/index.ts +191 -0
  194. package/src/interceptors/WebSocket/utils/bindEvent.test.ts +27 -0
  195. package/src/interceptors/WebSocket/utils/bindEvent.ts +21 -0
  196. package/src/interceptors/WebSocket/utils/events.test.ts +101 -0
  197. package/src/interceptors/WebSocket/utils/events.ts +94 -0
  198. package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +746 -0
  199. package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +121 -0
  200. package/src/interceptors/XMLHttpRequest/index.ts +61 -0
  201. package/src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts +51 -0
  202. package/src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts +17 -0
  203. package/src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts +12 -0
  204. package/src/interceptors/XMLHttpRequest/utils/concateArrayBuffer.test.ts +12 -0
  205. package/src/interceptors/XMLHttpRequest/utils/createEvent.test.ts +26 -0
  206. package/src/interceptors/XMLHttpRequest/utils/createEvent.ts +41 -0
  207. package/src/interceptors/XMLHttpRequest/utils/createResponse.ts +49 -0
  208. package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.test.ts +164 -0
  209. package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts +16 -0
  210. package/src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts +14 -0
  211. package/src/interceptors/fetch/index.ts +214 -0
  212. package/src/interceptors/fetch/utils/brotli-decompress.browser.ts +14 -0
  213. package/src/interceptors/fetch/utils/brotli-decompress.ts +31 -0
  214. package/src/interceptors/fetch/utils/createNetworkError.ts +5 -0
  215. package/src/interceptors/fetch/utils/decompression.ts +85 -0
  216. package/src/interceptors/fetch/utils/followRedirect.ts +114 -0
  217. package/src/presets/browser.ts +11 -0
  218. package/src/presets/node.ts +13 -0
  219. package/src/utils/bufferUtils.test.ts +21 -0
  220. package/src/utils/bufferUtils.ts +22 -0
  221. package/src/utils/canParseUrl.ts +13 -0
  222. package/src/utils/cloneObject.test.ts +94 -0
  223. package/src/utils/cloneObject.ts +36 -0
  224. package/src/utils/createProxy.test.ts +164 -0
  225. package/src/utils/createProxy.ts +104 -0
  226. package/src/utils/emitAsync.ts +25 -0
  227. package/src/utils/fetchUtils.ts +119 -0
  228. package/src/utils/findPropertySource.test.ts +27 -0
  229. package/src/utils/findPropertySource.ts +20 -0
  230. package/src/utils/getCleanUrl.test.ts +32 -0
  231. package/src/utils/getCleanUrl.ts +6 -0
  232. package/src/utils/getUrlByRequestOptions.test.ts +163 -0
  233. package/src/utils/getUrlByRequestOptions.ts +152 -0
  234. package/src/utils/getValueBySymbol.test.ts +14 -0
  235. package/src/utils/getValueBySymbol.ts +19 -0
  236. package/src/utils/handleRequest.ts +205 -0
  237. package/src/utils/hasConfigurableGlobal.test.ts +83 -0
  238. package/src/utils/hasConfigurableGlobal.ts +34 -0
  239. package/src/utils/isNodeLikeError.ts +13 -0
  240. package/src/utils/isObject.test.ts +21 -0
  241. package/src/utils/isObject.ts +8 -0
  242. package/src/utils/isPropertyAccessible.ts +19 -0
  243. package/src/utils/nextTick.ts +11 -0
  244. package/src/utils/node/index.ts +39 -0
  245. package/src/utils/parseJson.test.ts +10 -0
  246. package/src/utils/parseJson.ts +12 -0
  247. package/src/utils/resolveWebSocketUrl.ts +45 -0
  248. package/src/utils/responseUtils.ts +59 -0
@@ -0,0 +1,724 @@
1
+ import net from 'node:net'
2
+ import {
3
+ type HeadersCallback,
4
+ HTTPParser,
5
+ type RequestHeadersCompleteCallback,
6
+ type ResponseHeadersCompleteCallback,
7
+ } from '_http_common'
8
+ import { STATUS_CODES, IncomingMessage, ServerResponse } from 'node:http'
9
+ import { Readable } from 'node:stream'
10
+ import { invariant } from 'outvariant'
11
+ import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
12
+ import { MockSocket } from '../Socket/MockSocket'
13
+ import type { NormalizedSocketWriteArgs } from '../Socket/utils/normalizeSocketWriteArgs'
14
+ import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
15
+ import { baseUrlFromConnectionOptions } from '../Socket/utils/baseUrlFromConnectionOptions'
16
+ import { createRequestId } from '../../createRequestId'
17
+ import { getRawFetchHeaders } from './utils/recordRawHeaders'
18
+ import { FetchResponse } from '../../utils/fetchUtils'
19
+ import { setRawRequest } from '../../getRawRequest'
20
+ import { setRawRequestBodyStream } from '../../utils/node'
21
+ import { freeParser } from './utils/parserUtils'
22
+
23
+ type HttpConnectionOptions = any
24
+
25
+ export type MockHttpSocketRequestCallback = (args: {
26
+ requestId: string
27
+ request: Request
28
+ socket: MockHttpSocket
29
+ }) => void
30
+
31
+ export type MockHttpSocketResponseCallback = (args: {
32
+ requestId: string
33
+ request: Request
34
+ response: Response
35
+ isMockedResponse: boolean
36
+ socket: MockHttpSocket
37
+ }) => Promise<void>
38
+
39
+ interface MockHttpSocketOptions {
40
+ connectionOptions: HttpConnectionOptions
41
+ createConnection: () => net.Socket
42
+ onRequest: MockHttpSocketRequestCallback
43
+ onResponse: MockHttpSocketResponseCallback
44
+ }
45
+
46
+ export const kRequestId = Symbol('kRequestId')
47
+
48
+ export class MockHttpSocket extends MockSocket {
49
+ private connectionOptions: HttpConnectionOptions
50
+ private createConnection: () => net.Socket
51
+ private baseUrl: URL
52
+
53
+ private onRequest: MockHttpSocketRequestCallback
54
+ private onResponse: MockHttpSocketResponseCallback
55
+ private responseListenersPromise?: Promise<void>
56
+
57
+ private requestRawHeadersBuffer: Array<string> = []
58
+ private responseRawHeadersBuffer: Array<string> = []
59
+ private writeBuffer: Array<NormalizedSocketWriteArgs> = []
60
+ private request?: Request
61
+ private requestParser: HTTPParser<0>
62
+ private requestStream?: Readable
63
+ private shouldKeepAlive?: boolean
64
+
65
+ private socketState: 'unknown' | 'mock' | 'passthrough' = 'unknown'
66
+ private responseParser: HTTPParser<1>
67
+ private responseStream?: Readable
68
+ private originalSocket?: net.Socket
69
+
70
+ constructor(options: MockHttpSocketOptions) {
71
+ super({
72
+ write: (chunk, encoding, callback) => {
73
+ // Buffer the writes so they can be flushed in case of the original connection
74
+ // and when reading the request body in the interceptor. If the connection has
75
+ // been established, no need to buffer the chunks anymore, they will be forwarded.
76
+ if (this.socketState !== 'passthrough') {
77
+ this.writeBuffer.push([chunk, encoding, callback])
78
+ }
79
+
80
+ if (chunk) {
81
+ /**
82
+ * Forward any writes to the mock socket to the underlying original socket.
83
+ * This ensures functional duplex connections, like WebSocket.
84
+ * @see https://github.com/mswjs/interceptors/issues/682
85
+ */
86
+ if (this.socketState === 'passthrough') {
87
+ this.originalSocket?.write(chunk, encoding, callback)
88
+ }
89
+
90
+ this.requestParser.execute(
91
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)
92
+ )
93
+ }
94
+ },
95
+ read: (chunk) => {
96
+ if (chunk !== null) {
97
+ /**
98
+ * @todo We need to free the parser if the connection has been
99
+ * upgraded to a non-HTTP protocol. It won't be able to parse data
100
+ * from that point onward anyway. No need to keep it in memory.
101
+ */
102
+ this.responseParser.execute(
103
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
104
+ )
105
+ }
106
+ },
107
+ })
108
+
109
+ this.connectionOptions = options.connectionOptions
110
+ this.createConnection = options.createConnection
111
+ this.onRequest = options.onRequest
112
+ this.onResponse = options.onResponse
113
+
114
+ this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions)
115
+
116
+ // Request parser.
117
+ this.requestParser = new HTTPParser()
118
+ this.requestParser.initialize(HTTPParser.REQUEST, {})
119
+ this.requestParser[HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this)
120
+ this.requestParser[HTTPParser.kOnHeadersComplete] =
121
+ this.onRequestStart.bind(this)
122
+ this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this)
123
+ this.requestParser[HTTPParser.kOnMessageComplete] =
124
+ this.onRequestEnd.bind(this)
125
+
126
+ // Response parser.
127
+ this.responseParser = new HTTPParser()
128
+ this.responseParser.initialize(HTTPParser.RESPONSE, {})
129
+ this.responseParser[HTTPParser.kOnHeaders] =
130
+ this.onResponseHeaders.bind(this)
131
+ this.responseParser[HTTPParser.kOnHeadersComplete] =
132
+ this.onResponseStart.bind(this)
133
+ this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this)
134
+ this.responseParser[HTTPParser.kOnMessageComplete] =
135
+ this.onResponseEnd.bind(this)
136
+
137
+ // Once the socket is finished, nothing can write to it
138
+ // anymore. It has also flushed any buffered chunks.
139
+ this.once('finish', () => freeParser(this.requestParser, this))
140
+
141
+ if (this.baseUrl.protocol === 'https:') {
142
+ Reflect.set(this, 'encrypted', true)
143
+ // The server certificate is not the same as a CA
144
+ // passed to the TLS socket connection options.
145
+ Reflect.set(this, 'authorized', false)
146
+ Reflect.set(this, 'getProtocol', () => 'TLSv1.3')
147
+ Reflect.set(this, 'getSession', () => undefined)
148
+ Reflect.set(this, 'isSessionReused', () => false)
149
+ Reflect.set(this, 'getCipher', () => ({
150
+ name: 'AES256-SHA',
151
+ standardName: 'TLS_RSA_WITH_AES_256_CBC_SHA',
152
+ version: 'TLSv1.3',
153
+ }))
154
+ }
155
+ }
156
+
157
+ public emit(event: string | symbol, ...args: any[]): boolean {
158
+ const emitEvent = super.emit.bind(this, event as any, ...args)
159
+
160
+ if (this.responseListenersPromise) {
161
+ this.responseListenersPromise.finally(emitEvent)
162
+ return this.listenerCount(event) > 0
163
+ }
164
+
165
+ return emitEvent()
166
+ }
167
+
168
+ public destroy(error?: Error | undefined): this {
169
+ // Destroy the response parser when the socket gets destroyed.
170
+ // Normally, we should listen to the "close" event but it
171
+ // can be suppressed by using the "emitClose: false" option.
172
+ freeParser(this.responseParser, this)
173
+
174
+ if (error) {
175
+ this.emit('error', error)
176
+ }
177
+
178
+ return super.destroy(error)
179
+ }
180
+
181
+ /**
182
+ * Establish this Socket connection as-is and pipe
183
+ * its data/events through this Socket.
184
+ */
185
+ public passthrough(): void {
186
+ this.socketState = 'passthrough'
187
+
188
+ if (this.destroyed) {
189
+ return
190
+ }
191
+
192
+ const socket = this.createConnection()
193
+ this.originalSocket = socket
194
+
195
+ /**
196
+ * @note Inherit the original socket's connection handle.
197
+ * Without this, each push to the mock socket results in a
198
+ * new "connection" listener being added (i.e. buffering pushes).
199
+ * @see https://github.com/nodejs/node/blob/b18153598b25485ce4f54d0c5cb830a9457691ee/lib/net.js#L734
200
+ */
201
+ if ('_handle' in socket) {
202
+ Object.defineProperty(this, '_handle', {
203
+ value: socket._handle,
204
+ enumerable: true,
205
+ writable: true,
206
+ })
207
+ }
208
+
209
+ // The client-facing socket can be destroyed in two ways:
210
+ // 1. The developer destroys the socket.
211
+ // 2. The passthrough socket "close" is forwarded to the socket.
212
+ this.once('close', () => {
213
+ socket.removeAllListeners()
214
+
215
+ // If the closure didn't originate from the passthrough socket, destroy it.
216
+ if (!socket.destroyed) {
217
+ socket.destroy()
218
+ }
219
+
220
+ this.originalSocket = undefined
221
+ })
222
+
223
+ this.address = socket.address.bind(socket)
224
+
225
+ // Flush the buffered "socket.write()" calls onto
226
+ // the original socket instance (i.e. write request body).
227
+ // Exhaust the "requestBuffer" in case this Socket
228
+ // gets reused for different requests.
229
+ let writeArgs: NormalizedSocketWriteArgs | undefined
230
+ let headersWritten = false
231
+
232
+ while ((writeArgs = this.writeBuffer.shift())) {
233
+ if (writeArgs !== undefined) {
234
+ if (!headersWritten) {
235
+ const [chunk, encoding, callback] = writeArgs
236
+ const chunkString = chunk.toString()
237
+ const chunkBeforeRequestHeaders = chunkString.slice(
238
+ 0,
239
+ chunkString.indexOf('\r\n') + 2
240
+ )
241
+ const chunkAfterRequestHeaders = chunkString.slice(
242
+ chunk.indexOf('\r\n\r\n')
243
+ )
244
+ const rawRequestHeaders = getRawFetchHeaders(this.request!.headers)
245
+ const requestHeadersString = rawRequestHeaders
246
+ // Skip the internal request ID deduplication header.
247
+ .filter(([name]) => {
248
+ return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME
249
+ })
250
+ .map(([name, value]) => `${name}: ${value}`)
251
+ .join('\r\n')
252
+
253
+ // Modify the HTTP request message headers
254
+ // to reflect any changes to the request headers
255
+ // from the "request" event listener.
256
+ const headersChunk = `${chunkBeforeRequestHeaders}${requestHeadersString}${chunkAfterRequestHeaders}`
257
+ socket.write(headersChunk, encoding, callback)
258
+ headersWritten = true
259
+ continue
260
+ }
261
+
262
+ socket.write(...writeArgs)
263
+ }
264
+ }
265
+
266
+ // Forward TLS Socket properties onto this Socket instance
267
+ // in the case of a TLS/SSL connection.
268
+ if (Reflect.get(socket, 'encrypted')) {
269
+ const tlsProperties = [
270
+ 'encrypted',
271
+ 'authorized',
272
+ 'getProtocol',
273
+ 'getSession',
274
+ 'isSessionReused',
275
+ 'getCipher',
276
+ ]
277
+
278
+ tlsProperties.forEach((propertyName) => {
279
+ Object.defineProperty(this, propertyName, {
280
+ enumerable: true,
281
+ get: () => {
282
+ const value = Reflect.get(socket, propertyName)
283
+ return typeof value === 'function' ? value.bind(socket) : value
284
+ },
285
+ })
286
+ })
287
+ }
288
+
289
+ socket
290
+ .on('lookup', (...args) => this.emit('lookup', ...args))
291
+ .on('connect', () => {
292
+ this.connecting = socket.connecting
293
+ this.emit('connect')
294
+ })
295
+ .on('secureConnect', () => this.emit('secureConnect'))
296
+ .on('secure', () => this.emit('secure'))
297
+ .on('session', (session) => this.emit('session', session))
298
+ .on('ready', () => this.emit('ready'))
299
+ .on('drain', () => this.emit('drain'))
300
+ .on('data', (chunk) => {
301
+ // Push the original response to this socket
302
+ // so it triggers the HTTP response parser. This unifies
303
+ // the handling pipeline for original and mocked response.
304
+ this.push(chunk)
305
+ })
306
+ .on('error', (error) => {
307
+ Reflect.set(this, '_hadError', Reflect.get(socket, '_hadError'))
308
+ this.emit('error', error)
309
+ })
310
+ .on('resume', () => this.emit('resume'))
311
+ .on('timeout', () => this.emit('timeout'))
312
+ .on('prefinish', () => this.emit('prefinish'))
313
+ .on('finish', () => this.emit('finish'))
314
+ .on('close', (hadError) => this.emit('close', hadError))
315
+ .on('end', () => this.emit('end'))
316
+ }
317
+
318
+ /**
319
+ * Convert the given Fetch API `Response` instance to an
320
+ * HTTP message and push it to the socket.
321
+ */
322
+ public async respondWith(response: Response): Promise<void> {
323
+ // Ignore the mocked response if the socket has been destroyed
324
+ // (e.g. aborted or timed out),
325
+ if (this.destroyed) {
326
+ return
327
+ }
328
+
329
+ // Prevent recursive calls.
330
+ invariant(
331
+ this.socketState !== 'mock',
332
+ '[MockHttpSocket] Failed to respond to the "%s %s" request with "%s %s": the request has already been handled',
333
+ this.request?.method,
334
+ this.request?.url,
335
+ response.status,
336
+ response.statusText
337
+ )
338
+
339
+ // Handle "type: error" responses.
340
+ if (isPropertyAccessible(response, 'type') && response.type === 'error') {
341
+ this.errorWith(new TypeError('Network error'))
342
+ return
343
+ }
344
+
345
+ // First, emit all the connection events
346
+ // to emulate a successful connection.
347
+ this.mockConnect()
348
+ this.socketState = 'mock'
349
+
350
+ // Flush the write buffer to trigger write callbacks
351
+ // if it hasn't been flushed already (e.g. someone started reading request stream).
352
+ this.flushWriteBuffer()
353
+
354
+ // Create a `ServerResponse` instance to delegate HTTP message parsing,
355
+ // Transfer-Encoding, and other things to Node.js internals.
356
+ const serverResponse = new ServerResponse(new IncomingMessage(this))
357
+
358
+ /**
359
+ * Assign a mock socket instance to the server response to
360
+ * spy on the response chunk writes. Push the transformed response chunks
361
+ * to this `MockHttpSocket` instance to trigger the "data" event.
362
+ * @note Providing the same `MockSocket` instance when creating `ServerResponse`
363
+ * does not have the same effect.
364
+ * @see https://github.com/nodejs/node/blob/10099bb3f7fd97bb9dd9667188426866b3098e07/test/parallel/test-http-server-response-standalone.js#L32
365
+ */
366
+ serverResponse.assignSocket(
367
+ new MockSocket({
368
+ write: (chunk, encoding, callback) => {
369
+ this.push(chunk, encoding)
370
+ callback?.()
371
+ },
372
+ read() {},
373
+ })
374
+ )
375
+
376
+ /**
377
+ * @note Remove the `Connection` and `Date` response headers
378
+ * injected by `ServerResponse` by default. Those are required
379
+ * from the server but the interceptor is NOT technically a server.
380
+ * It's confusing to add response headers that the developer didn't
381
+ * specify themselves. They can always add these if they wish.
382
+ * @see https://www.rfc-editor.org/rfc/rfc9110#field.date
383
+ * @see https://www.rfc-editor.org/rfc/rfc9110#field.connection
384
+ */
385
+ serverResponse.removeHeader('connection')
386
+ serverResponse.removeHeader('date')
387
+
388
+ const rawResponseHeaders = getRawFetchHeaders(response.headers)
389
+
390
+ /**
391
+ * @note Call `.writeHead` in order to set the raw response headers
392
+ * in the same case as they were provided by the developer. Using
393
+ * `.setHeader()`/`.appendHeader()` normalizes header names.
394
+ */
395
+ serverResponse.writeHead(
396
+ response.status,
397
+ response.statusText || STATUS_CODES[response.status],
398
+ rawResponseHeaders
399
+ )
400
+
401
+ // If the developer destroy the socket, gracefully destroy the response.
402
+ this.once('error', () => {
403
+ serverResponse.destroy()
404
+ })
405
+
406
+ if (response.body) {
407
+ try {
408
+ const reader = response.body.getReader()
409
+
410
+ while (true) {
411
+ const { done, value } = await reader.read()
412
+
413
+ if (done) {
414
+ serverResponse.end()
415
+ break
416
+ }
417
+
418
+ serverResponse.write(value)
419
+ }
420
+ } catch (error) {
421
+ if (error instanceof Error) {
422
+ serverResponse.destroy()
423
+ /**
424
+ * @note Destroy the request socket gracefully.
425
+ * Response stream errors do NOT produce request errors.
426
+ */
427
+ this.destroy()
428
+ return
429
+ }
430
+
431
+ serverResponse.destroy()
432
+ throw error
433
+ }
434
+ } else {
435
+ serverResponse.end()
436
+ }
437
+
438
+ // Close the socket if the connection wasn't marked as keep-alive.
439
+ if (!this.shouldKeepAlive) {
440
+ this.emit('readable')
441
+
442
+ /**
443
+ * @todo @fixme This is likely a hack.
444
+ * Since we push null to the socket, it never propagates to the
445
+ * parser, and the parser never calls "onResponseEnd" to close
446
+ * the response stream. We are closing the stream here manually
447
+ * but that shouldn't be the case.
448
+ */
449
+ this.responseStream?.push(null)
450
+ this.push(null)
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Close this socket connection with the given error.
456
+ */
457
+ public errorWith(error?: Error): void {
458
+ this.destroy(error)
459
+ }
460
+
461
+ private mockConnect(): void {
462
+ // Calling this method immediately puts the socket
463
+ // into the connected state.
464
+ this.connecting = false
465
+
466
+ const isIPv6 =
467
+ net.isIPv6(this.connectionOptions.hostname) ||
468
+ this.connectionOptions.family === 6
469
+ const addressInfo = {
470
+ address: isIPv6 ? '::1' : '127.0.0.1',
471
+ family: isIPv6 ? 'IPv6' : 'IPv4',
472
+ port: this.connectionOptions.port,
473
+ }
474
+ // Return fake address information for the socket.
475
+ this.address = () => addressInfo
476
+ this.emit(
477
+ 'lookup',
478
+ null,
479
+ addressInfo.address,
480
+ addressInfo.family === 'IPv6' ? 6 : 4,
481
+ this.connectionOptions.host
482
+ )
483
+ this.emit('connect')
484
+ this.emit('ready')
485
+
486
+ if (this.baseUrl.protocol === 'https:') {
487
+ this.emit('secure')
488
+ this.emit('secureConnect')
489
+
490
+ // A single TLS connection is represented by two "session" events.
491
+ this.emit(
492
+ 'session',
493
+ this.connectionOptions.session ||
494
+ Buffer.from('mock-session-renegotiate')
495
+ )
496
+ this.emit('session', Buffer.from('mock-session-resume'))
497
+ }
498
+ }
499
+
500
+ private flushWriteBuffer(): void {
501
+ for (const writeCall of this.writeBuffer) {
502
+ if (typeof writeCall[2] === 'function') {
503
+ writeCall[2]()
504
+ /**
505
+ * @note Remove the callback from the write call
506
+ * so it doesn't get called twice on passthrough
507
+ * if `request.end()` was called within `request.write()`.
508
+ * @see https://github.com/mswjs/interceptors/issues/684
509
+ */
510
+ writeCall[2] = undefined
511
+ }
512
+ }
513
+ }
514
+
515
+ /**
516
+ * This callback might be called when the request is "slow":
517
+ * - Request headers were fragmented across multiple TCP packages;
518
+ * - Request headers were too large to be processed in a single run
519
+ * (e.g. more than 30 request headers).
520
+ * @note This is called before request start.
521
+ */
522
+ private onRequestHeaders: HeadersCallback = (rawHeaders) => {
523
+ this.requestRawHeadersBuffer.push(...rawHeaders)
524
+ }
525
+
526
+ private onRequestStart: RequestHeadersCompleteCallback = (
527
+ versionMajor,
528
+ versionMinor,
529
+ rawHeaders,
530
+ _,
531
+ path,
532
+ __,
533
+ ___,
534
+ ____,
535
+ shouldKeepAlive
536
+ ) => {
537
+ this.shouldKeepAlive = shouldKeepAlive
538
+
539
+ const url = new URL(path || '', this.baseUrl)
540
+ const method = this.connectionOptions.method?.toUpperCase() || 'GET'
541
+ const headers = FetchResponse.parseRawHeaders([
542
+ ...this.requestRawHeadersBuffer,
543
+ ...(rawHeaders || []),
544
+ ])
545
+ this.requestRawHeadersBuffer.length = 0
546
+
547
+ const canHaveBody = method !== 'GET' && method !== 'HEAD'
548
+
549
+ // Translate the basic authorization in the URL to the request header.
550
+ // Constructing a Request instance with a URL containing auth is no-op.
551
+ if (url.username || url.password) {
552
+ if (!headers.has('authorization')) {
553
+ headers.set('authorization', `Basic ${url.username}:${url.password}`)
554
+ }
555
+ url.username = ''
556
+ url.password = ''
557
+ }
558
+
559
+ // Create a new stream for each request.
560
+ // If this Socket is reused for multiple requests,
561
+ // this ensures that each request gets its own stream.
562
+ // One Socket instance can only handle one request at a time.
563
+ this.requestStream = new Readable({
564
+ /**
565
+ * @note Provide the `read()` method so a `Readable` could be
566
+ * used as the actual request body (the stream calls "read()").
567
+ * We control the queue in the onRequestBody/End functions.
568
+ */
569
+ read: () => {
570
+ // If the user attempts to read the request body,
571
+ // flush the write buffer to trigger the callbacks.
572
+ // This way, if the request stream ends in the write callback,
573
+ // it will indeed end correctly.
574
+ this.flushWriteBuffer()
575
+ },
576
+ })
577
+
578
+ const requestId = createRequestId()
579
+ this.request = new Request(url, {
580
+ method,
581
+ headers,
582
+ credentials: 'same-origin',
583
+ // @ts-expect-error Undocumented Fetch property.
584
+ duplex: canHaveBody ? 'half' : undefined,
585
+ body: canHaveBody ? (Readable.toWeb(this.requestStream!) as any) : null,
586
+ })
587
+
588
+ Reflect.set(this.request, kRequestId, requestId)
589
+
590
+ // Set the raw `http.ClientRequest` instance on the request instance.
591
+ // This is useful for cases like getting the raw headers of the request.
592
+ setRawRequest(this.request, Reflect.get(this, '_httpMessage'))
593
+
594
+ // Create a copy of the request body stream and store it on the request.
595
+ // This is only needed for the consumers who wish to read the request body stream
596
+ // of requests that cannot have a body per Fetch API specification (i.e. GET, HEAD).
597
+ setRawRequestBodyStream(this.request, this.requestStream)
598
+
599
+ // Skip handling the request that's already being handled
600
+ // by another (parent) interceptor. For example, XMLHttpRequest
601
+ // is often implemented via ClientRequest in Node.js (e.g. JSDOM).
602
+ // In that case, XHR interceptor will bubble down to the ClientRequest
603
+ // interceptor. No need to try to handle that request again.
604
+ /**
605
+ * @fixme Stop relying on the "X-Request-Id" request header
606
+ * to figure out if one interceptor has been invoked within another.
607
+ * @see https://github.com/mswjs/interceptors/issues/378
608
+ */
609
+ if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
610
+ this.passthrough()
611
+ return
612
+ }
613
+
614
+ this.onRequest({
615
+ requestId,
616
+ request: this.request,
617
+ socket: this,
618
+ })
619
+ }
620
+
621
+ private onRequestBody(chunk: Buffer): void {
622
+ invariant(
623
+ this.requestStream,
624
+ 'Failed to write to a request stream: stream does not exist'
625
+ )
626
+
627
+ this.requestStream.push(chunk)
628
+ }
629
+
630
+ private onRequestEnd(): void {
631
+ // Request end can be called for requests without body.
632
+ if (this.requestStream) {
633
+ this.requestStream.push(null)
634
+ }
635
+ }
636
+
637
+ /**
638
+ * This callback might be called when the response is "slow":
639
+ * - Response headers were fragmented across multiple TCP packages;
640
+ * - Response headers were too large to be processed in a single run
641
+ * (e.g. more than 30 response headers).
642
+ * @note This is called before response start.
643
+ */
644
+ private onResponseHeaders: HeadersCallback = (rawHeaders) => {
645
+ this.responseRawHeadersBuffer.push(...rawHeaders)
646
+ }
647
+
648
+ private onResponseStart: ResponseHeadersCompleteCallback = (
649
+ versionMajor,
650
+ versionMinor,
651
+ rawHeaders,
652
+ method,
653
+ url,
654
+ status,
655
+ statusText
656
+ ) => {
657
+ const headers = FetchResponse.parseRawHeaders([
658
+ ...this.responseRawHeadersBuffer,
659
+ ...(rawHeaders || []),
660
+ ])
661
+ this.responseRawHeadersBuffer.length = 0
662
+
663
+ const response = new FetchResponse(
664
+ /**
665
+ * @note The Fetch API response instance exposed to the consumer
666
+ * is created over the response stream of the HTTP parser. It is NOT
667
+ * related to the Socket instance. This way, you can read response body
668
+ * in response listener while the Socket instance delays the emission
669
+ * of "end" and other events until those response listeners are finished.
670
+ */
671
+ FetchResponse.isResponseWithBody(status)
672
+ ? (Readable.toWeb(
673
+ (this.responseStream = new Readable({ read() {} }))
674
+ ) as any)
675
+ : null,
676
+ {
677
+ url,
678
+ status,
679
+ statusText,
680
+ headers,
681
+ }
682
+ )
683
+
684
+ invariant(
685
+ this.request,
686
+ 'Failed to handle a response: request does not exist'
687
+ )
688
+
689
+ FetchResponse.setUrl(this.request.url, response)
690
+
691
+ /**
692
+ * @fixme Stop relying on the "X-Request-Id" request header
693
+ * to figure out if one interceptor has been invoked within another.
694
+ * @see https://github.com/mswjs/interceptors/issues/378
695
+ */
696
+ if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
697
+ return
698
+ }
699
+
700
+ this.responseListenersPromise = this.onResponse({
701
+ response,
702
+ isMockedResponse: this.socketState === 'mock',
703
+ requestId: Reflect.get(this.request, kRequestId),
704
+ request: this.request,
705
+ socket: this,
706
+ })
707
+ }
708
+
709
+ private onResponseBody(chunk: Buffer) {
710
+ invariant(
711
+ this.responseStream,
712
+ 'Failed to write to a response stream: stream does not exist'
713
+ )
714
+
715
+ this.responseStream.push(chunk)
716
+ }
717
+
718
+ private onResponseEnd(): void {
719
+ // Response end can be called for responses without body.
720
+ if (this.responseStream) {
721
+ this.responseStream.push(null)
722
+ }
723
+ }
724
+ }