@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,746 @@
1
+ import { invariant } from 'outvariant'
2
+ import { isNodeProcess } from 'is-node-process'
3
+ import type { Logger } from '@open-draft/logger'
4
+ import { concatArrayBuffer } from './utils/concatArrayBuffer'
5
+ import { createEvent } from './utils/createEvent'
6
+ import {
7
+ decodeBuffer,
8
+ encodeBuffer,
9
+ toArrayBuffer,
10
+ } from '../../utils/bufferUtils'
11
+ import { createProxy } from '../../utils/createProxy'
12
+ import { isDomParserSupportedType } from './utils/isDomParserSupportedType'
13
+ import { parseJson } from '../../utils/parseJson'
14
+ import { createResponse } from './utils/createResponse'
15
+ import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
16
+ import { createRequestId } from '../../createRequestId'
17
+ import { getBodyByteLength } from './utils/getBodyByteLength'
18
+ import { setRawRequest } from '../../getRawRequest'
19
+
20
+ const kIsRequestHandled = Symbol('kIsRequestHandled')
21
+ const IS_NODE = isNodeProcess()
22
+ const kFetchRequest = Symbol('kFetchRequest')
23
+
24
+ /**
25
+ * An `XMLHttpRequest` instance controller that allows us
26
+ * to handle any given request instance (e.g. responding to it).
27
+ */
28
+ export class XMLHttpRequestController {
29
+ public request: XMLHttpRequest
30
+ public requestId: string
31
+ public onRequest?: (
32
+ this: XMLHttpRequestController,
33
+ args: {
34
+ request: Request
35
+ requestId: string
36
+ }
37
+ ) => Promise<void>
38
+ public onResponse?: (
39
+ this: XMLHttpRequestController,
40
+ args: {
41
+ response: Response
42
+ isMockedResponse: boolean
43
+ request: Request
44
+ requestId: string
45
+ }
46
+ ) => void;
47
+
48
+ [kIsRequestHandled]: boolean;
49
+ [kFetchRequest]?: Request
50
+ private method: string = 'GET'
51
+ private url: URL = null as any
52
+ private requestHeaders: Headers
53
+ private responseBuffer: Uint8Array
54
+ private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>
55
+ private uploadEvents: Map<
56
+ keyof XMLHttpRequestEventTargetEventMap,
57
+ Array<Function>
58
+ >
59
+
60
+ constructor(
61
+ readonly initialRequest: XMLHttpRequest,
62
+ public logger: Logger
63
+ ) {
64
+ this[kIsRequestHandled] = false
65
+
66
+ this.events = new Map()
67
+ this.uploadEvents = new Map()
68
+ this.requestId = createRequestId()
69
+ this.requestHeaders = new Headers()
70
+ this.responseBuffer = new Uint8Array()
71
+
72
+ this.request = createProxy(initialRequest, {
73
+ setProperty: ([propertyName, nextValue], invoke) => {
74
+ switch (propertyName) {
75
+ case 'ontimeout': {
76
+ const eventName = propertyName.slice(
77
+ 2
78
+ ) as keyof XMLHttpRequestEventTargetEventMap
79
+
80
+ /**
81
+ * @note Proxy callbacks to event listeners because JSDOM has trouble
82
+ * translating these properties to callbacks. It seemed to be operating
83
+ * on events exclusively.
84
+ */
85
+ this.request.addEventListener(eventName, nextValue as any)
86
+
87
+ return invoke()
88
+ }
89
+
90
+ default: {
91
+ return invoke()
92
+ }
93
+ }
94
+ },
95
+ methodCall: ([methodName, args], invoke) => {
96
+ switch (methodName) {
97
+ case 'open': {
98
+ const [method, url] = args as [string, string | undefined]
99
+
100
+ if (typeof url === 'undefined') {
101
+ this.method = 'GET'
102
+ this.url = toAbsoluteUrl(method)
103
+ } else {
104
+ this.method = method
105
+ this.url = toAbsoluteUrl(url)
106
+ }
107
+
108
+ this.logger = this.logger.extend(`${this.method} ${this.url.href}`)
109
+ this.logger.info('open', this.method, this.url.href)
110
+
111
+ return invoke()
112
+ }
113
+
114
+ case 'addEventListener': {
115
+ const [eventName, listener] = args as [
116
+ keyof XMLHttpRequestEventTargetEventMap,
117
+ Function,
118
+ ]
119
+
120
+ this.registerEvent(eventName, listener)
121
+ this.logger.info('addEventListener', eventName, listener)
122
+
123
+ return invoke()
124
+ }
125
+
126
+ case 'setRequestHeader': {
127
+ const [name, value] = args as [string, string]
128
+ this.requestHeaders.set(name, value)
129
+
130
+ this.logger.info('setRequestHeader', name, value)
131
+
132
+ return invoke()
133
+ }
134
+
135
+ case 'send': {
136
+ const [body] = args as [
137
+ body?: XMLHttpRequestBodyInit | Document | null,
138
+ ]
139
+
140
+ this.request.addEventListener('load', () => {
141
+ if (typeof this.onResponse !== 'undefined') {
142
+ // Create a Fetch API Response representation of whichever
143
+ // response this XMLHttpRequest received. Note those may
144
+ // be either a mocked and the original response.
145
+ const fetchResponse = createResponse(
146
+ this.request,
147
+ /**
148
+ * The `response` property is the right way to read
149
+ * the ambiguous response body, as the request's "responseType" may differ.
150
+ * @see https://xhr.spec.whatwg.org/#the-response-attribute
151
+ */
152
+ this.request.response
153
+ )
154
+
155
+ // Notify the consumer about the response.
156
+ this.onResponse.call(this, {
157
+ response: fetchResponse,
158
+ isMockedResponse: this[kIsRequestHandled],
159
+ request: fetchRequest,
160
+ requestId: this.requestId!,
161
+ })
162
+ }
163
+ })
164
+
165
+ const requestBody =
166
+ typeof body === 'string' ? encodeBuffer(body) : body
167
+
168
+ // Delegate request handling to the consumer.
169
+ const fetchRequest = this.toFetchApiRequest(requestBody)
170
+ this[kFetchRequest] = fetchRequest.clone()
171
+
172
+ /**
173
+ * @note Start request handling on the next tick so that the user
174
+ * could add event listeners for "loadend" before the interceptor fires it.
175
+ */
176
+ queueMicrotask(() => {
177
+ const onceRequestSettled =
178
+ this.onRequest?.call(this, {
179
+ request: fetchRequest,
180
+ requestId: this.requestId!,
181
+ }) || Promise.resolve()
182
+
183
+ onceRequestSettled.finally(() => {
184
+ // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.
185
+ if (!this[kIsRequestHandled]) {
186
+ this.logger.info(
187
+ 'request callback settled but request has not been handled (readystate %d), performing as-is...',
188
+ this.request.readyState
189
+ )
190
+
191
+ /**
192
+ * @note Set the intercepted request ID on the original request in Node.js
193
+ * so that if it triggers any other interceptors, they don't attempt
194
+ * to process it once again.
195
+ *
196
+ * For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
197
+ * and we don't want for both XHR and ClientRequest interceptors to
198
+ * handle the same request at the same time (e.g. emit the "response" event twice).
199
+ */
200
+ if (IS_NODE) {
201
+ this.request.setRequestHeader(
202
+ INTERNAL_REQUEST_ID_HEADER_NAME,
203
+ this.requestId!
204
+ )
205
+ }
206
+
207
+ return invoke()
208
+ }
209
+ })
210
+ })
211
+
212
+ break
213
+ }
214
+
215
+ default: {
216
+ return invoke()
217
+ }
218
+ }
219
+ },
220
+ })
221
+
222
+ /**
223
+ * Proxy the `.upload` property to gather the event listeners/callbacks.
224
+ */
225
+ define(
226
+ this.request,
227
+ 'upload',
228
+ createProxy(this.request.upload, {
229
+ setProperty: ([propertyName, nextValue], invoke) => {
230
+ switch (propertyName) {
231
+ case 'onloadstart':
232
+ case 'onprogress':
233
+ case 'onaboart':
234
+ case 'onerror':
235
+ case 'onload':
236
+ case 'ontimeout':
237
+ case 'onloadend': {
238
+ const eventName = propertyName.slice(
239
+ 2
240
+ ) as keyof XMLHttpRequestEventTargetEventMap
241
+
242
+ this.registerUploadEvent(eventName, nextValue as Function)
243
+ }
244
+ }
245
+
246
+ return invoke()
247
+ },
248
+ methodCall: ([methodName, args], invoke) => {
249
+ switch (methodName) {
250
+ case 'addEventListener': {
251
+ const [eventName, listener] = args as [
252
+ keyof XMLHttpRequestEventTargetEventMap,
253
+ Function,
254
+ ]
255
+ this.registerUploadEvent(eventName, listener)
256
+ this.logger.info('upload.addEventListener', eventName, listener)
257
+
258
+ return invoke()
259
+ }
260
+ }
261
+ },
262
+ })
263
+ )
264
+ }
265
+
266
+ private registerEvent(
267
+ eventName: keyof XMLHttpRequestEventTargetEventMap,
268
+ listener: Function
269
+ ): void {
270
+ const prevEvents = this.events.get(eventName) || []
271
+ const nextEvents = prevEvents.concat(listener)
272
+ this.events.set(eventName, nextEvents)
273
+
274
+ this.logger.info('registered event "%s"', eventName, listener)
275
+ }
276
+
277
+ private registerUploadEvent(
278
+ eventName: keyof XMLHttpRequestEventTargetEventMap,
279
+ listener: Function
280
+ ): void {
281
+ const prevEvents = this.uploadEvents.get(eventName) || []
282
+ const nextEvents = prevEvents.concat(listener)
283
+ this.uploadEvents.set(eventName, nextEvents)
284
+
285
+ this.logger.info('registered upload event "%s"', eventName, listener)
286
+ }
287
+
288
+ /**
289
+ * Responds to the current request with the given
290
+ * Fetch API `Response` instance.
291
+ */
292
+ public async respondWith(response: Response): Promise<void> {
293
+ /**
294
+ * @note Since `XMLHttpRequestController` delegates the handling of the responses
295
+ * to the "load" event listener that doesn't distinguish between the mocked and original
296
+ * responses, mark the request that had a mocked response with a corresponding symbol.
297
+ *
298
+ * Mark this request as having a mocked response immediately since
299
+ * calculating request/response total body length is asynchronous.
300
+ */
301
+ this[kIsRequestHandled] = true
302
+
303
+ /**
304
+ * Dispatch request upload events for requests with a body.
305
+ * @see https://github.com/mswjs/interceptors/issues/573
306
+ */
307
+ if (this[kFetchRequest]) {
308
+ const totalRequestBodyLength = await getBodyByteLength(
309
+ this[kFetchRequest]
310
+ )
311
+
312
+ this.trigger('loadstart', this.request.upload, {
313
+ loaded: 0,
314
+ total: totalRequestBodyLength,
315
+ })
316
+ this.trigger('progress', this.request.upload, {
317
+ loaded: totalRequestBodyLength,
318
+ total: totalRequestBodyLength,
319
+ })
320
+ this.trigger('load', this.request.upload, {
321
+ loaded: totalRequestBodyLength,
322
+ total: totalRequestBodyLength,
323
+ })
324
+
325
+ this.trigger('loadend', this.request.upload, {
326
+ loaded: totalRequestBodyLength,
327
+ total: totalRequestBodyLength,
328
+ })
329
+ }
330
+
331
+ this.logger.info(
332
+ 'responding with a mocked response: %d %s',
333
+ response.status,
334
+ response.statusText
335
+ )
336
+
337
+ define(this.request, 'status', response.status)
338
+ define(this.request, 'statusText', response.statusText)
339
+ define(this.request, 'responseURL', this.url.href)
340
+
341
+ this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {
342
+ apply: (_, __, args: [name: string]) => {
343
+ this.logger.info('getResponseHeader', args[0])
344
+
345
+ if (this.request.readyState < this.request.HEADERS_RECEIVED) {
346
+ this.logger.info('headers not received yet, returning null')
347
+
348
+ // Headers not received yet, nothing to return.
349
+ return null
350
+ }
351
+
352
+ const headerValue = response.headers.get(args[0])
353
+ this.logger.info(
354
+ 'resolved response header "%s" to',
355
+ args[0],
356
+ headerValue
357
+ )
358
+
359
+ return headerValue
360
+ },
361
+ })
362
+
363
+ this.request.getAllResponseHeaders = new Proxy(
364
+ this.request.getAllResponseHeaders,
365
+ {
366
+ apply: () => {
367
+ this.logger.info('getAllResponseHeaders')
368
+
369
+ if (this.request.readyState < this.request.HEADERS_RECEIVED) {
370
+ this.logger.info('headers not received yet, returning empty string')
371
+
372
+ // Headers not received yet, nothing to return.
373
+ return ''
374
+ }
375
+
376
+ const headersList = Array.from(response.headers.entries())
377
+ const allHeaders = headersList
378
+ .map(([headerName, headerValue]) => {
379
+ return `${headerName}: ${headerValue}`
380
+ })
381
+ .join('\r\n')
382
+
383
+ this.logger.info('resolved all response headers to', allHeaders)
384
+
385
+ return allHeaders
386
+ },
387
+ }
388
+ )
389
+
390
+ // Update the response getters to resolve against the mocked response.
391
+ Object.defineProperties(this.request, {
392
+ response: {
393
+ enumerable: true,
394
+ configurable: false,
395
+ get: () => this.response,
396
+ },
397
+ responseText: {
398
+ enumerable: true,
399
+ configurable: false,
400
+ get: () => this.responseText,
401
+ },
402
+ responseXML: {
403
+ enumerable: true,
404
+ configurable: false,
405
+ get: () => this.responseXML,
406
+ },
407
+ })
408
+
409
+ const totalResponseBodyLength = await getBodyByteLength(response.clone())
410
+
411
+ this.logger.info('calculated response body length', totalResponseBodyLength)
412
+
413
+ this.trigger('loadstart', this.request, {
414
+ loaded: 0,
415
+ total: totalResponseBodyLength,
416
+ })
417
+
418
+ this.setReadyState(this.request.HEADERS_RECEIVED)
419
+ this.setReadyState(this.request.LOADING)
420
+
421
+ const finalizeResponse = () => {
422
+ this.logger.info('finalizing the mocked response...')
423
+
424
+ this.setReadyState(this.request.DONE)
425
+
426
+ this.trigger('load', this.request, {
427
+ loaded: this.responseBuffer.byteLength,
428
+ total: totalResponseBodyLength,
429
+ })
430
+
431
+ this.trigger('loadend', this.request, {
432
+ loaded: this.responseBuffer.byteLength,
433
+ total: totalResponseBodyLength,
434
+ })
435
+ }
436
+
437
+ if (response.body) {
438
+ this.logger.info('mocked response has body, streaming...')
439
+
440
+ const reader = response.body.getReader()
441
+
442
+ const readNextResponseBodyChunk = async () => {
443
+ const { value, done } = await reader.read()
444
+
445
+ if (done) {
446
+ this.logger.info('response body stream done!')
447
+ finalizeResponse()
448
+ return
449
+ }
450
+
451
+ if (value) {
452
+ this.logger.info('read response body chunk:', value)
453
+ this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)
454
+
455
+ this.trigger('progress', this.request, {
456
+ loaded: this.responseBuffer.byteLength,
457
+ total: totalResponseBodyLength,
458
+ })
459
+ }
460
+
461
+ readNextResponseBodyChunk()
462
+ }
463
+
464
+ readNextResponseBodyChunk()
465
+ } else {
466
+ finalizeResponse()
467
+ }
468
+ }
469
+
470
+ private responseBufferToText(): string {
471
+ return decodeBuffer(this.responseBuffer)
472
+ }
473
+
474
+ get response(): unknown {
475
+ this.logger.info(
476
+ 'getResponse (responseType: %s)',
477
+ this.request.responseType
478
+ )
479
+
480
+ if (this.request.readyState !== this.request.DONE) {
481
+ return null
482
+ }
483
+
484
+ switch (this.request.responseType) {
485
+ case 'json': {
486
+ const responseJson = parseJson(this.responseBufferToText())
487
+ this.logger.info('resolved response JSON', responseJson)
488
+
489
+ return responseJson
490
+ }
491
+
492
+ case 'arraybuffer': {
493
+ const arrayBuffer = toArrayBuffer(this.responseBuffer)
494
+ this.logger.info('resolved response ArrayBuffer', arrayBuffer)
495
+
496
+ return arrayBuffer
497
+ }
498
+
499
+ case 'blob': {
500
+ const mimeType =
501
+ this.request.getResponseHeader('Content-Type') || 'text/plain'
502
+ const responseBlob = new Blob([this.responseBufferToText()], {
503
+ type: mimeType,
504
+ })
505
+
506
+ this.logger.info(
507
+ 'resolved response Blob (mime type: %s)',
508
+ responseBlob,
509
+ mimeType
510
+ )
511
+
512
+ return responseBlob
513
+ }
514
+
515
+ default: {
516
+ const responseText = this.responseBufferToText()
517
+ this.logger.info(
518
+ 'resolving "%s" response type as text',
519
+ this.request.responseType,
520
+ responseText
521
+ )
522
+
523
+ return responseText
524
+ }
525
+ }
526
+ }
527
+
528
+ get responseText(): string {
529
+ /**
530
+ * Throw when trying to read the response body as text when the
531
+ * "responseType" doesn't expect text. This just respects the spec better.
532
+ * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute
533
+ */
534
+ invariant(
535
+ this.request.responseType === '' || this.request.responseType === 'text',
536
+ 'InvalidStateError: The object is in invalid state.'
537
+ )
538
+
539
+ if (
540
+ this.request.readyState !== this.request.LOADING &&
541
+ this.request.readyState !== this.request.DONE
542
+ ) {
543
+ return ''
544
+ }
545
+
546
+ const responseText = this.responseBufferToText()
547
+ this.logger.info('getResponseText: "%s"', responseText)
548
+
549
+ return responseText
550
+ }
551
+
552
+ get responseXML(): Document | null {
553
+ invariant(
554
+ this.request.responseType === '' ||
555
+ this.request.responseType === 'document',
556
+ 'InvalidStateError: The object is in invalid state.'
557
+ )
558
+
559
+ if (this.request.readyState !== this.request.DONE) {
560
+ return null
561
+ }
562
+
563
+ const contentType = this.request.getResponseHeader('Content-Type') || ''
564
+
565
+ if (typeof DOMParser === 'undefined') {
566
+ console.warn(
567
+ 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'
568
+ )
569
+ return null
570
+ }
571
+
572
+ if (isDomParserSupportedType(contentType)) {
573
+ return new DOMParser().parseFromString(
574
+ this.responseBufferToText(),
575
+ contentType
576
+ )
577
+ }
578
+
579
+ return null
580
+ }
581
+
582
+ public errorWith(error?: Error): void {
583
+ /**
584
+ * @note Mark this request as handled even if it received a mock error.
585
+ * This prevents the controller from trying to perform this request as-is.
586
+ */
587
+ this[kIsRequestHandled] = true
588
+ this.logger.info('responding with an error')
589
+
590
+ this.setReadyState(this.request.DONE)
591
+ this.trigger('error', this.request)
592
+ this.trigger('loadend', this.request)
593
+ }
594
+
595
+ /**
596
+ * Transitions this request's `readyState` to the given one.
597
+ */
598
+ private setReadyState(nextReadyState: number): void {
599
+ this.logger.info(
600
+ 'setReadyState: %d -> %d',
601
+ this.request.readyState,
602
+ nextReadyState
603
+ )
604
+
605
+ if (this.request.readyState === nextReadyState) {
606
+ this.logger.info('ready state identical, skipping transition...')
607
+ return
608
+ }
609
+
610
+ define(this.request, 'readyState', nextReadyState)
611
+
612
+ this.logger.info('set readyState to: %d', nextReadyState)
613
+
614
+ if (nextReadyState !== this.request.UNSENT) {
615
+ this.logger.info('triggering "readystatechange" event...')
616
+
617
+ this.trigger('readystatechange', this.request)
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Triggers given event on the `XMLHttpRequest` instance.
623
+ */
624
+ private trigger<
625
+ EventName extends keyof (XMLHttpRequestEventTargetEventMap & {
626
+ readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
627
+ }),
628
+ >(
629
+ eventName: EventName,
630
+ target: XMLHttpRequest | XMLHttpRequestUpload,
631
+ options?: ProgressEventInit
632
+ ): void {
633
+ const callback = (target as XMLHttpRequest)[`on${eventName}`]
634
+ const event = createEvent(target, eventName, options)
635
+
636
+ this.logger.info('trigger "%s"', eventName, options || '')
637
+
638
+ // Invoke direct callbacks.
639
+ if (typeof callback === 'function') {
640
+ this.logger.info('found a direct "%s" callback, calling...', eventName)
641
+ callback.call(target as XMLHttpRequest, event)
642
+ }
643
+
644
+ // Invoke event listeners.
645
+ const events =
646
+ target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events
647
+
648
+ for (const [registeredEventName, listeners] of events) {
649
+ if (registeredEventName === eventName) {
650
+ this.logger.info(
651
+ 'found %d listener(s) for "%s" event, calling...',
652
+ listeners.length,
653
+ eventName
654
+ )
655
+
656
+ listeners.forEach((listener) => listener.call(target, event))
657
+ }
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.
663
+ */
664
+ private toFetchApiRequest(
665
+ body: XMLHttpRequestBodyInit | Document | null | undefined
666
+ ): Request {
667
+ this.logger.info('converting request to a Fetch API Request...')
668
+
669
+ // If the `Document` is used as the body of this XMLHttpRequest,
670
+ // set its inner text as the Fetch API Request body.
671
+ const resolvedBody =
672
+ body instanceof Document ? body.documentElement.innerText : body
673
+
674
+ const fetchRequest = new Request(this.url.href, {
675
+ method: this.method,
676
+ headers: this.requestHeaders,
677
+ /**
678
+ * @see https://xhr.spec.whatwg.org/#cross-origin-credentials
679
+ */
680
+ credentials: this.request.withCredentials ? 'include' : 'same-origin',
681
+ body: ['GET', 'HEAD'].includes(this.method.toUpperCase())
682
+ ? null
683
+ : resolvedBody,
684
+ })
685
+
686
+ const proxyHeaders = createProxy(fetchRequest.headers, {
687
+ methodCall: ([methodName, args], invoke) => {
688
+ // Forward the latest state of the internal request headers
689
+ // because the interceptor might have modified them
690
+ // without responding to the request.
691
+ switch (methodName) {
692
+ case 'append':
693
+ case 'set': {
694
+ const [headerName, headerValue] = args as [string, string]
695
+ this.request.setRequestHeader(headerName, headerValue)
696
+ break
697
+ }
698
+
699
+ case 'delete': {
700
+ const [headerName] = args as [string]
701
+ console.warn(
702
+ `XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`
703
+ )
704
+ break
705
+ }
706
+ }
707
+
708
+ return invoke()
709
+ },
710
+ })
711
+ define(fetchRequest, 'headers', proxyHeaders)
712
+ setRawRequest(fetchRequest, this.request)
713
+
714
+ this.logger.info('converted request to a Fetch API Request!', fetchRequest)
715
+
716
+ return fetchRequest
717
+ }
718
+ }
719
+
720
+ function toAbsoluteUrl(url: string | URL): URL {
721
+ /**
722
+ * @note XMLHttpRequest interceptor may run in environments
723
+ * that implement XMLHttpRequest but don't implement "location"
724
+ * (for example, React Native). If that's the case, return the
725
+ * input URL as-is (nothing to be relative to).
726
+ * @see https://github.com/mswjs/msw/issues/1777
727
+ */
728
+ if (typeof location === 'undefined') {
729
+ return new URL(url)
730
+ }
731
+
732
+ return new URL(url.toString(), location.href)
733
+ }
734
+
735
+ function define(
736
+ target: object,
737
+ property: string | symbol,
738
+ value: unknown
739
+ ): void {
740
+ Reflect.defineProperty(target, property, {
741
+ // Ensure writable properties to allow redefining readonly properties.
742
+ writable: true,
743
+ enumerable: true,
744
+ value,
745
+ })
746
+ }