dsh-model-proxy-switch 0.0.0-stage → 1.0.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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -2
  3. package/client.js +473 -0
  4. package/cordis.patch.yml +7 -0
  5. package/icon.svg +13 -0
  6. package/index.js +341 -0
  7. package/locale/en.json +25 -0
  8. package/locale/zh.json +25 -0
  9. package/package.json +55 -4
  10. package/vendor/http-proxy.js +542 -0
  11. package/vendor/node_modules/undici/LICENSE +21 -0
  12. package/vendor/node_modules/undici/README.md +741 -0
  13. package/vendor/node_modules/undici/docs/docs/api/Agent.md +84 -0
  14. package/vendor/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
  15. package/vendor/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
  16. package/vendor/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
  17. package/vendor/node_modules/undici/docs/docs/api/Client.md +290 -0
  18. package/vendor/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
  19. package/vendor/node_modules/undici/docs/docs/api/Connector.md +115 -0
  20. package/vendor/node_modules/undici/docs/docs/api/ContentType.md +57 -0
  21. package/vendor/node_modules/undici/docs/docs/api/Cookies.md +128 -0
  22. package/vendor/node_modules/undici/docs/docs/api/Debug.md +62 -0
  23. package/vendor/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
  24. package/vendor/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
  25. package/vendor/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
  26. package/vendor/node_modules/undici/docs/docs/api/Errors.md +49 -0
  27. package/vendor/node_modules/undici/docs/docs/api/EventSource.md +45 -0
  28. package/vendor/node_modules/undici/docs/docs/api/Fetch.md +60 -0
  29. package/vendor/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
  30. package/vendor/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
  31. package/vendor/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
  32. package/vendor/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
  33. package/vendor/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
  34. package/vendor/node_modules/undici/docs/docs/api/MockClient.md +81 -0
  35. package/vendor/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
  36. package/vendor/node_modules/undici/docs/docs/api/MockPool.md +555 -0
  37. package/vendor/node_modules/undici/docs/docs/api/Pool.md +84 -0
  38. package/vendor/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
  39. package/vendor/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
  40. package/vendor/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
  41. package/vendor/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
  42. package/vendor/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
  43. package/vendor/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
  44. package/vendor/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
  45. package/vendor/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
  46. package/vendor/node_modules/undici/docs/docs/api/Util.md +25 -0
  47. package/vendor/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
  48. package/vendor/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
  49. package/vendor/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
  50. package/vendor/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
  51. package/vendor/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
  52. package/vendor/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
  53. package/vendor/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
  54. package/vendor/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
  55. package/vendor/node_modules/undici/index-fetch.js +65 -0
  56. package/vendor/node_modules/undici/index.d.ts +3 -0
  57. package/vendor/node_modules/undici/index.js +234 -0
  58. package/vendor/node_modules/undici/lib/api/abort-signal.js +59 -0
  59. package/vendor/node_modules/undici/lib/api/api-connect.js +110 -0
  60. package/vendor/node_modules/undici/lib/api/api-pipeline.js +252 -0
  61. package/vendor/node_modules/undici/lib/api/api-request.js +214 -0
  62. package/vendor/node_modules/undici/lib/api/api-stream.js +209 -0
  63. package/vendor/node_modules/undici/lib/api/api-upgrade.js +111 -0
  64. package/vendor/node_modules/undici/lib/api/index.js +7 -0
  65. package/vendor/node_modules/undici/lib/api/readable.js +580 -0
  66. package/vendor/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
  67. package/vendor/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
  68. package/vendor/node_modules/undici/lib/core/connect.js +159 -0
  69. package/vendor/node_modules/undici/lib/core/constants.js +143 -0
  70. package/vendor/node_modules/undici/lib/core/diagnostics.js +227 -0
  71. package/vendor/node_modules/undici/lib/core/errors.js +477 -0
  72. package/vendor/node_modules/undici/lib/core/request.js +485 -0
  73. package/vendor/node_modules/undici/lib/core/socks5-client.js +422 -0
  74. package/vendor/node_modules/undici/lib/core/socks5-utils.js +212 -0
  75. package/vendor/node_modules/undici/lib/core/symbols.js +76 -0
  76. package/vendor/node_modules/undici/lib/core/tree.js +160 -0
  77. package/vendor/node_modules/undici/lib/core/util.js +992 -0
  78. package/vendor/node_modules/undici/lib/dispatcher/agent.js +162 -0
  79. package/vendor/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
  80. package/vendor/node_modules/undici/lib/dispatcher/client-h1.js +1744 -0
  81. package/vendor/node_modules/undici/lib/dispatcher/client-h2.js +1056 -0
  82. package/vendor/node_modules/undici/lib/dispatcher/client.js +669 -0
  83. package/vendor/node_modules/undici/lib/dispatcher/dispatcher-base.js +196 -0
  84. package/vendor/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
  85. package/vendor/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
  86. package/vendor/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
  87. package/vendor/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
  88. package/vendor/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
  89. package/vendor/node_modules/undici/lib/dispatcher/pool.js +118 -0
  90. package/vendor/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
  91. package/vendor/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
  92. package/vendor/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
  93. package/vendor/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
  94. package/vendor/node_modules/undici/lib/encoding/index.js +33 -0
  95. package/vendor/node_modules/undici/lib/global.js +59 -0
  96. package/vendor/node_modules/undici/lib/handler/cache-handler.js +824 -0
  97. package/vendor/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
  98. package/vendor/node_modules/undici/lib/handler/decorator-handler.js +67 -0
  99. package/vendor/node_modules/undici/lib/handler/deduplication-handler.js +504 -0
  100. package/vendor/node_modules/undici/lib/handler/redirect-handler.js +245 -0
  101. package/vendor/node_modules/undici/lib/handler/retry-handler.js +504 -0
  102. package/vendor/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
  103. package/vendor/node_modules/undici/lib/handler/wrap-handler.js +105 -0
  104. package/vendor/node_modules/undici/lib/interceptor/cache.js +621 -0
  105. package/vendor/node_modules/undici/lib/interceptor/decompress.js +561 -0
  106. package/vendor/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
  107. package/vendor/node_modules/undici/lib/interceptor/dns.js +571 -0
  108. package/vendor/node_modules/undici/lib/interceptor/dump.js +99 -0
  109. package/vendor/node_modules/undici/lib/interceptor/redirect.js +21 -0
  110. package/vendor/node_modules/undici/lib/interceptor/response-error.js +95 -0
  111. package/vendor/node_modules/undici/lib/interceptor/retry.js +19 -0
  112. package/vendor/node_modules/undici/lib/llhttp/.gitkeep +0 -0
  113. package/vendor/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
  114. package/vendor/node_modules/undici/lib/llhttp/constants.js +531 -0
  115. package/vendor/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
  116. package/vendor/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
  117. package/vendor/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
  118. package/vendor/node_modules/undici/lib/llhttp/utils.js +12 -0
  119. package/vendor/node_modules/undici/lib/mock/mock-agent.js +232 -0
  120. package/vendor/node_modules/undici/lib/mock/mock-call-history.js +248 -0
  121. package/vendor/node_modules/undici/lib/mock/mock-client.js +68 -0
  122. package/vendor/node_modules/undici/lib/mock/mock-errors.js +29 -0
  123. package/vendor/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
  124. package/vendor/node_modules/undici/lib/mock/mock-pool.js +68 -0
  125. package/vendor/node_modules/undici/lib/mock/mock-symbols.js +32 -0
  126. package/vendor/node_modules/undici/lib/mock/mock-utils.js +486 -0
  127. package/vendor/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
  128. package/vendor/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
  129. package/vendor/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
  130. package/vendor/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
  131. package/vendor/node_modules/undici/lib/util/cache.js +676 -0
  132. package/vendor/node_modules/undici/lib/util/date.js +670 -0
  133. package/vendor/node_modules/undici/lib/util/promise.js +28 -0
  134. package/vendor/node_modules/undici/lib/util/runtime-features.js +124 -0
  135. package/vendor/node_modules/undici/lib/util/stats.js +32 -0
  136. package/vendor/node_modules/undici/lib/util/timers.js +425 -0
  137. package/vendor/node_modules/undici/lib/web/cache/cache.js +864 -0
  138. package/vendor/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
  139. package/vendor/node_modules/undici/lib/web/cache/util.js +45 -0
  140. package/vendor/node_modules/undici/lib/web/cookies/constants.js +12 -0
  141. package/vendor/node_modules/undici/lib/web/cookies/index.js +201 -0
  142. package/vendor/node_modules/undici/lib/web/cookies/parse.js +315 -0
  143. package/vendor/node_modules/undici/lib/web/cookies/util.js +371 -0
  144. package/vendor/node_modules/undici/lib/web/eventsource/eventsource-stream.js +575 -0
  145. package/vendor/node_modules/undici/lib/web/eventsource/eventsource.js +504 -0
  146. package/vendor/node_modules/undici/lib/web/eventsource/util.js +29 -0
  147. package/vendor/node_modules/undici/lib/web/fetch/LICENSE +21 -0
  148. package/vendor/node_modules/undici/lib/web/fetch/body.js +509 -0
  149. package/vendor/node_modules/undici/lib/web/fetch/constants.js +131 -0
  150. package/vendor/node_modules/undici/lib/web/fetch/data-url.js +596 -0
  151. package/vendor/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
  152. package/vendor/node_modules/undici/lib/web/fetch/formdata.js +259 -0
  153. package/vendor/node_modules/undici/lib/web/fetch/global.js +40 -0
  154. package/vendor/node_modules/undici/lib/web/fetch/headers.js +719 -0
  155. package/vendor/node_modules/undici/lib/web/fetch/index.js +2413 -0
  156. package/vendor/node_modules/undici/lib/web/fetch/request.js +1115 -0
  157. package/vendor/node_modules/undici/lib/web/fetch/response.js +641 -0
  158. package/vendor/node_modules/undici/lib/web/fetch/util.js +1522 -0
  159. package/vendor/node_modules/undici/lib/web/infra/index.js +243 -0
  160. package/vendor/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
  161. package/vendor/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
  162. package/vendor/node_modules/undici/lib/web/webidl/index.js +1006 -0
  163. package/vendor/node_modules/undici/lib/web/websocket/connection.js +339 -0
  164. package/vendor/node_modules/undici/lib/web/websocket/constants.js +126 -0
  165. package/vendor/node_modules/undici/lib/web/websocket/events.js +331 -0
  166. package/vendor/node_modules/undici/lib/web/websocket/frame.js +133 -0
  167. package/vendor/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
  168. package/vendor/node_modules/undici/lib/web/websocket/receiver.js +520 -0
  169. package/vendor/node_modules/undici/lib/web/websocket/sender.js +109 -0
  170. package/vendor/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
  171. package/vendor/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
  172. package/vendor/node_modules/undici/lib/web/websocket/util.js +347 -0
  173. package/vendor/node_modules/undici/lib/web/websocket/websocket.js +758 -0
  174. package/vendor/node_modules/undici/package.json +152 -0
  175. package/vendor/node_modules/undici/scripts/strip-comments.js +10 -0
  176. package/vendor/node_modules/undici/types/README.md +6 -0
  177. package/vendor/node_modules/undici/types/agent.d.ts +32 -0
  178. package/vendor/node_modules/undici/types/api.d.ts +43 -0
  179. package/vendor/node_modules/undici/types/balanced-pool.d.ts +30 -0
  180. package/vendor/node_modules/undici/types/cache-interceptor.d.ts +179 -0
  181. package/vendor/node_modules/undici/types/cache.d.ts +36 -0
  182. package/vendor/node_modules/undici/types/client-stats.d.ts +15 -0
  183. package/vendor/node_modules/undici/types/client.d.ts +153 -0
  184. package/vendor/node_modules/undici/types/connector.d.ts +36 -0
  185. package/vendor/node_modules/undici/types/content-type.d.ts +21 -0
  186. package/vendor/node_modules/undici/types/cookies.d.ts +30 -0
  187. package/vendor/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
  188. package/vendor/node_modules/undici/types/dispatcher.d.ts +275 -0
  189. package/vendor/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
  190. package/vendor/node_modules/undici/types/errors.d.ts +177 -0
  191. package/vendor/node_modules/undici/types/eventsource.d.ts +66 -0
  192. package/vendor/node_modules/undici/types/fetch.d.ts +231 -0
  193. package/vendor/node_modules/undici/types/formdata.d.ts +114 -0
  194. package/vendor/node_modules/undici/types/global-dispatcher.d.ts +9 -0
  195. package/vendor/node_modules/undici/types/global-origin.d.ts +7 -0
  196. package/vendor/node_modules/undici/types/h2c-client.d.ts +73 -0
  197. package/vendor/node_modules/undici/types/handlers.d.ts +14 -0
  198. package/vendor/node_modules/undici/types/header.d.ts +160 -0
  199. package/vendor/node_modules/undici/types/index.d.ts +91 -0
  200. package/vendor/node_modules/undici/types/interceptors.d.ts +82 -0
  201. package/vendor/node_modules/undici/types/mock-agent.d.ts +68 -0
  202. package/vendor/node_modules/undici/types/mock-call-history.d.ts +111 -0
  203. package/vendor/node_modules/undici/types/mock-client.d.ts +27 -0
  204. package/vendor/node_modules/undici/types/mock-errors.d.ts +12 -0
  205. package/vendor/node_modules/undici/types/mock-interceptor.d.ts +94 -0
  206. package/vendor/node_modules/undici/types/mock-pool.d.ts +27 -0
  207. package/vendor/node_modules/undici/types/patch.d.ts +29 -0
  208. package/vendor/node_modules/undici/types/pool-stats.d.ts +19 -0
  209. package/vendor/node_modules/undici/types/pool.d.ts +41 -0
  210. package/vendor/node_modules/undici/types/proxy-agent.d.ts +29 -0
  211. package/vendor/node_modules/undici/types/readable.d.ts +68 -0
  212. package/vendor/node_modules/undici/types/retry-agent.d.ts +8 -0
  213. package/vendor/node_modules/undici/types/retry-handler.d.ts +125 -0
  214. package/vendor/node_modules/undici/types/round-robin-pool.d.ts +41 -0
  215. package/vendor/node_modules/undici/types/snapshot-agent.d.ts +109 -0
  216. package/vendor/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
  217. package/vendor/node_modules/undici/types/util.d.ts +18 -0
  218. package/vendor/node_modules/undici/types/utility.d.ts +7 -0
  219. package/vendor/node_modules/undici/types/webidl.d.ts +347 -0
  220. package/vendor/node_modules/undici/types/websocket.d.ts +188 -0
@@ -0,0 +1,124 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+
5
+ /**
6
+ * This takes care of revalidation requests we send to the origin. If we get
7
+ * a response indicating that what we have is cached (via a HTTP 304), we can
8
+ * continue using the cached value. Otherwise, we'll receive the new response
9
+ * here, which we then just pass on to the next handler (most likely a
10
+ * CacheHandler). Note that this assumes the proper headers were already
11
+ * included in the request to tell the origin that we want to revalidate the
12
+ * response (i.e. if-modified-since or if-none-match).
13
+ *
14
+ * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
15
+ *
16
+ * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
17
+ */
18
+ class CacheRevalidationHandler {
19
+ #successful = false
20
+
21
+ /**
22
+ * @type {((success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void) | null}
23
+ */
24
+ #callback
25
+
26
+ /**
27
+ * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
28
+ */
29
+ #handler
30
+
31
+ #context
32
+
33
+ /**
34
+ * @type {boolean}
35
+ */
36
+ #allowErrorStatusCodes
37
+
38
+ /**
39
+ * @param {(success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void} callback Function to call if the cached value is valid
40
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
41
+ * @param {boolean} allowErrorStatusCodes
42
+ */
43
+ constructor (callback, handler, allowErrorStatusCodes) {
44
+ if (typeof callback !== 'function') {
45
+ throw new TypeError('callback must be a function')
46
+ }
47
+
48
+ this.#callback = callback
49
+ this.#handler = handler
50
+ this.#allowErrorStatusCodes = allowErrorStatusCodes
51
+ }
52
+
53
+ onRequestStart (_, context) {
54
+ this.#successful = false
55
+ this.#context = context
56
+ }
57
+
58
+ onRequestUpgrade (controller, statusCode, headers, socket) {
59
+ this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
60
+ }
61
+
62
+ onResponseStart (
63
+ controller,
64
+ statusCode,
65
+ headers,
66
+ statusMessage
67
+ ) {
68
+ assert(this.#callback != null)
69
+
70
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
71
+ // https://datatracker.ietf.org/doc/html/rfc5861#section-4
72
+ this.#successful = statusCode === 304 ||
73
+ (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
74
+ this.#callback(this.#successful, this.#context, statusCode, headers)
75
+ this.#callback = null
76
+
77
+ if (this.#successful) {
78
+ return true
79
+ }
80
+
81
+ this.#handler.onRequestStart?.(controller, this.#context)
82
+ this.#handler.onResponseStart?.(
83
+ controller,
84
+ statusCode,
85
+ headers,
86
+ statusMessage
87
+ )
88
+ }
89
+
90
+ onResponseData (controller, chunk) {
91
+ if (this.#successful) {
92
+ return
93
+ }
94
+
95
+ return this.#handler.onResponseData?.(controller, chunk)
96
+ }
97
+
98
+ onResponseEnd (controller, trailers) {
99
+ if (this.#successful) {
100
+ return
101
+ }
102
+
103
+ this.#handler.onResponseEnd?.(controller, trailers)
104
+ }
105
+
106
+ onResponseError (controller, err) {
107
+ if (this.#successful) {
108
+ return
109
+ }
110
+
111
+ if (this.#callback) {
112
+ this.#callback(false)
113
+ this.#callback = null
114
+ }
115
+
116
+ if (typeof this.#handler.onResponseError === 'function') {
117
+ this.#handler.onResponseError(controller, err)
118
+ } else {
119
+ throw err
120
+ }
121
+ }
122
+ }
123
+
124
+ module.exports = CacheRevalidationHandler
@@ -0,0 +1,67 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const WrapHandler = require('./wrap-handler')
5
+
6
+ /**
7
+ * @deprecated
8
+ */
9
+ module.exports = class DecoratorHandler {
10
+ #handler
11
+ #onCompleteCalled = false
12
+ #onErrorCalled = false
13
+ #onResponseStartCalled = false
14
+
15
+ constructor (handler) {
16
+ if (typeof handler !== 'object' || handler === null) {
17
+ throw new TypeError('handler must be an object')
18
+ }
19
+ this.#handler = WrapHandler.wrap(handler)
20
+ }
21
+
22
+ onRequestStart (...args) {
23
+ this.#handler.onRequestStart?.(...args)
24
+ }
25
+
26
+ onRequestUpgrade (...args) {
27
+ assert(!this.#onCompleteCalled)
28
+ assert(!this.#onErrorCalled)
29
+
30
+ return this.#handler.onRequestUpgrade?.(...args)
31
+ }
32
+
33
+ onResponseStart (...args) {
34
+ assert(!this.#onCompleteCalled)
35
+ assert(!this.#onErrorCalled)
36
+ assert(!this.#onResponseStartCalled)
37
+
38
+ this.#onResponseStartCalled = true
39
+
40
+ return this.#handler.onResponseStart?.(...args)
41
+ }
42
+
43
+ onResponseData (...args) {
44
+ assert(!this.#onCompleteCalled)
45
+ assert(!this.#onErrorCalled)
46
+
47
+ return this.#handler.onResponseData?.(...args)
48
+ }
49
+
50
+ onResponseEnd (...args) {
51
+ assert(!this.#onCompleteCalled)
52
+ assert(!this.#onErrorCalled)
53
+
54
+ this.#onCompleteCalled = true
55
+ return this.#handler.onResponseEnd?.(...args)
56
+ }
57
+
58
+ onResponseError (...args) {
59
+ this.#onErrorCalled = true
60
+ return this.#handler.onResponseError?.(...args)
61
+ }
62
+
63
+ /**
64
+ * @deprecated
65
+ */
66
+ onBodySent () {}
67
+ }
@@ -0,0 +1,504 @@
1
+ 'use strict'
2
+
3
+ const { RequestAbortedError } = require('../core/errors')
4
+
5
+ /**
6
+ * @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
7
+ */
8
+
9
+ const DEFAULT_MAX_BUFFER_SIZE = 5 * 1024 * 1024
10
+
11
+ /**
12
+ * @typedef {Object} WaitingHandler
13
+ * @property {DispatchHandler} handler
14
+ * @property {import('../../types/dispatcher.d.ts').default.DispatchController} controller
15
+ * @property {Buffer[]} bufferedChunks
16
+ * @property {number} bufferedBytes
17
+ * @property {object | null} pendingTrailers
18
+ * @property {boolean} done
19
+ * @property {boolean} [pendingStart] onRequestStart deferred until the primary request's context is known
20
+ */
21
+
22
+ /**
23
+ * Handler that forwards response events to multiple waiting handlers.
24
+ * Used for request deduplication.
25
+ *
26
+ * @implements {DispatchHandler}
27
+ */
28
+ class DeduplicationHandler {
29
+ /**
30
+ * @type {DispatchHandler}
31
+ */
32
+ #primaryHandler
33
+
34
+ /**
35
+ * Whether the primary request has started (onRequestStart received). Until
36
+ * then the request context is unknown and cannot be given to waiting handlers.
37
+ * @type {boolean}
38
+ */
39
+ #requestStarted = false
40
+
41
+ /**
42
+ * The primary request context, replayed to waiting handlers so they observe
43
+ * the same redirect history as the primary.
44
+ * @type {any}
45
+ */
46
+ #context = null
47
+
48
+ /**
49
+ * @type {WaitingHandler[]}
50
+ */
51
+ #waitingHandlers = []
52
+
53
+ /**
54
+ * @type {number}
55
+ */
56
+ #maxBufferSize = DEFAULT_MAX_BUFFER_SIZE
57
+
58
+ /**
59
+ * @type {number}
60
+ */
61
+ #statusCode = 0
62
+
63
+ /**
64
+ * @type {Record<string, string | string[]>}
65
+ */
66
+ #headers = {}
67
+
68
+ /**
69
+ * @type {string}
70
+ */
71
+ #statusMessage = ''
72
+
73
+ /**
74
+ * @type {boolean}
75
+ */
76
+ #aborted = false
77
+
78
+ /**
79
+ * @type {boolean}
80
+ */
81
+ #responseStarted = false
82
+
83
+ /**
84
+ * @type {boolean}
85
+ */
86
+ #responseDataStarted = false
87
+
88
+ /**
89
+ * @type {boolean}
90
+ */
91
+ #completed = false
92
+
93
+ /**
94
+ * @type {import('../../types/dispatcher.d.ts').default.DispatchController | null}
95
+ */
96
+ #controller = null
97
+
98
+ /**
99
+ * @type {(() => void) | null}
100
+ */
101
+ #onComplete = null
102
+
103
+ /**
104
+ * @param {DispatchHandler} primaryHandler The primary handler
105
+ * @param {() => void} onComplete Callback when request completes
106
+ * @param {number} [maxBufferSize] Maximum paused buffer size per waiting handler
107
+ */
108
+ constructor (primaryHandler, onComplete, maxBufferSize = DEFAULT_MAX_BUFFER_SIZE) {
109
+ this.#primaryHandler = primaryHandler
110
+ this.#onComplete = onComplete
111
+ this.#maxBufferSize = maxBufferSize
112
+ }
113
+
114
+ /**
115
+ * Add a waiting handler that will receive response events.
116
+ * Returns false if deduplication can no longer safely attach this handler.
117
+ *
118
+ * @param {DispatchHandler} handler
119
+ * @returns {boolean}
120
+ */
121
+ addWaitingHandler (handler) {
122
+ if (this.#completed || this.#responseDataStarted) {
123
+ return false
124
+ }
125
+
126
+ const waitingHandler = this.#createWaitingHandler(handler)
127
+ const waitingController = waitingHandler.controller
128
+
129
+ try {
130
+ if (this.#requestStarted) {
131
+ // The primary request has started, so its context (including the
132
+ // redirect history) is known and can be delivered immediately.
133
+ handler.onRequestStart?.(waitingController, this.#context)
134
+
135
+ if (waitingController.aborted) {
136
+ waitingHandler.done = true
137
+ return true
138
+ }
139
+
140
+ if (this.#responseStarted) {
141
+ handler.onResponseStart?.(
142
+ waitingController,
143
+ this.#statusCode,
144
+ this.#headers,
145
+ this.#statusMessage
146
+ )
147
+ }
148
+ } else {
149
+ // The primary request has not started yet. Defer onRequestStart until
150
+ // its context is known, so the waiting handler observes the same
151
+ // redirect history as the primary (a cache handler relies on it to
152
+ // reject cross-origin redirect responses).
153
+ waitingHandler.pendingStart = true
154
+ }
155
+ } catch {
156
+ // Ignore errors from waiting handlers
157
+ waitingHandler.done = true
158
+ return true
159
+ }
160
+
161
+ if (!waitingController.aborted) {
162
+ this.#waitingHandlers.push(waitingHandler)
163
+ }
164
+
165
+ return true
166
+ }
167
+
168
+ /**
169
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
170
+ * @param {any} context
171
+ */
172
+ onRequestStart (controller, context) {
173
+ this.#controller = controller
174
+ if (!this.#requestStarted) {
175
+ this.#requestStarted = true
176
+ this.#context = context
177
+ // Deliver the now-known context to handlers that joined before the
178
+ // primary request started.
179
+ for (const waitingHandler of this.#waitingHandlers) {
180
+ if (waitingHandler.pendingStart && !waitingHandler.done && !waitingHandler.controller.aborted) {
181
+ waitingHandler.pendingStart = false
182
+ try {
183
+ waitingHandler.handler.onRequestStart?.(waitingHandler.controller, context)
184
+ } catch {
185
+ // Ignore errors from waiting handlers
186
+ waitingHandler.done = true
187
+ }
188
+ }
189
+ }
190
+ this.#pruneDoneWaitingHandlers()
191
+ }
192
+ this.#primaryHandler.onRequestStart?.(controller, context)
193
+ }
194
+
195
+ /**
196
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
197
+ * @param {number} statusCode
198
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} headers
199
+ * @param {Socket} socket
200
+ */
201
+ onRequestUpgrade (controller, statusCode, headers, socket) {
202
+ this.#primaryHandler.onRequestUpgrade?.(controller, statusCode, headers, socket)
203
+ }
204
+
205
+ /**
206
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
207
+ * @param {number} statusCode
208
+ * @param {Record<string, string | string[]>} headers
209
+ * @param {string} statusMessage
210
+ */
211
+ onResponseStart (controller, statusCode, headers, statusMessage) {
212
+ this.#responseStarted = true
213
+ this.#statusCode = statusCode
214
+ this.#headers = headers
215
+ this.#statusMessage = statusMessage
216
+
217
+ this.#primaryHandler.onResponseStart?.(controller, statusCode, headers, statusMessage)
218
+
219
+ for (const waitingHandler of this.#waitingHandlers) {
220
+ const { handler, controller: waitingController } = waitingHandler
221
+
222
+ if (waitingHandler.done || waitingController.aborted) {
223
+ waitingHandler.done = true
224
+ continue
225
+ }
226
+
227
+ try {
228
+ handler.onResponseStart?.(
229
+ waitingController,
230
+ statusCode,
231
+ headers,
232
+ statusMessage
233
+ )
234
+ } catch {
235
+ // Ignore errors from waiting handlers
236
+ }
237
+
238
+ if (waitingController.aborted) {
239
+ waitingHandler.done = true
240
+ }
241
+ }
242
+
243
+ this.#pruneDoneWaitingHandlers()
244
+ }
245
+
246
+ /**
247
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
248
+ * @param {Buffer} chunk
249
+ */
250
+ onResponseData (controller, chunk) {
251
+ if (this.#aborted || this.#completed) {
252
+ return
253
+ }
254
+
255
+ this.#responseDataStarted = true
256
+
257
+ this.#primaryHandler.onResponseData?.(controller, chunk)
258
+
259
+ for (const waitingHandler of this.#waitingHandlers) {
260
+ const { handler, controller: waitingController } = waitingHandler
261
+
262
+ if (waitingHandler.done || waitingController.aborted) {
263
+ waitingHandler.done = true
264
+ continue
265
+ }
266
+
267
+ if (waitingController.paused) {
268
+ this.#bufferWaitingChunk(waitingHandler, chunk)
269
+ continue
270
+ }
271
+
272
+ try {
273
+ handler.onResponseData?.(waitingController, chunk)
274
+ } catch {
275
+ // Ignore errors from waiting handlers
276
+ }
277
+
278
+ if (waitingController.aborted) {
279
+ waitingHandler.done = true
280
+ waitingHandler.bufferedChunks = []
281
+ waitingHandler.bufferedBytes = 0
282
+ }
283
+ }
284
+
285
+ this.#pruneDoneWaitingHandlers()
286
+ }
287
+
288
+ /**
289
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
290
+ * @param {object} trailers
291
+ */
292
+ onResponseEnd (controller, trailers) {
293
+ if (this.#aborted || this.#completed) {
294
+ return
295
+ }
296
+
297
+ this.#completed = true
298
+ this.#primaryHandler.onResponseEnd?.(controller, trailers)
299
+
300
+ for (const waitingHandler of this.#waitingHandlers) {
301
+ if (waitingHandler.done || waitingHandler.controller.aborted) {
302
+ waitingHandler.done = true
303
+ continue
304
+ }
305
+
306
+ this.#flushWaitingHandler(waitingHandler)
307
+
308
+ if (waitingHandler.done || waitingHandler.controller.aborted) {
309
+ waitingHandler.done = true
310
+ continue
311
+ }
312
+
313
+ if (waitingHandler.controller.paused && waitingHandler.bufferedChunks.length > 0) {
314
+ waitingHandler.pendingTrailers = trailers
315
+ continue
316
+ }
317
+
318
+ try {
319
+ waitingHandler.handler.onResponseEnd?.(waitingHandler.controller, trailers)
320
+ } catch {
321
+ // Ignore errors from waiting handlers
322
+ }
323
+
324
+ waitingHandler.done = true
325
+ }
326
+
327
+ this.#pruneDoneWaitingHandlers()
328
+ this.#onComplete?.()
329
+ }
330
+
331
+ /**
332
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchController} controller
333
+ * @param {Error} err
334
+ */
335
+ onResponseError (controller, err) {
336
+ if (this.#completed) {
337
+ return
338
+ }
339
+
340
+ this.#aborted = true
341
+ this.#completed = true
342
+
343
+ this.#primaryHandler.onResponseError?.(controller, err)
344
+
345
+ for (const waitingHandler of this.#waitingHandlers) {
346
+ this.#errorWaitingHandler(waitingHandler, err)
347
+ }
348
+
349
+ this.#waitingHandlers = []
350
+ this.#onComplete?.()
351
+ }
352
+
353
+ /**
354
+ * @param {DispatchHandler} handler
355
+ * @returns {WaitingHandler}
356
+ */
357
+ #createWaitingHandler (handler) {
358
+ /** @type {WaitingHandler} */
359
+ const waitingHandler = {
360
+ handler,
361
+ controller: null,
362
+ bufferedChunks: [],
363
+ bufferedBytes: 0,
364
+ pendingTrailers: null,
365
+ done: false,
366
+ pendingStart: false
367
+ }
368
+
369
+ const state = {
370
+ aborted: false,
371
+ paused: false,
372
+ reason: null
373
+ }
374
+
375
+ waitingHandler.controller = {
376
+ resume: () => {
377
+ if (state.aborted) {
378
+ return
379
+ }
380
+
381
+ state.paused = false
382
+ this.#flushWaitingHandler(waitingHandler)
383
+
384
+ if (
385
+ this.#completed &&
386
+ waitingHandler.pendingTrailers &&
387
+ waitingHandler.bufferedChunks.length === 0 &&
388
+ !state.paused &&
389
+ !state.aborted
390
+ ) {
391
+ try {
392
+ waitingHandler.handler.onResponseEnd?.(waitingHandler.controller, waitingHandler.pendingTrailers)
393
+ } catch {
394
+ // Ignore errors from waiting handlers
395
+ }
396
+
397
+ waitingHandler.pendingTrailers = null
398
+ waitingHandler.done = true
399
+ }
400
+
401
+ this.#pruneDoneWaitingHandlers()
402
+ },
403
+ pause: () => {
404
+ if (!state.aborted) {
405
+ state.paused = true
406
+ }
407
+ },
408
+ get paused () { return state.paused },
409
+ get aborted () { return state.aborted },
410
+ get reason () { return state.reason },
411
+ abort: (reason) => {
412
+ state.aborted = true
413
+ state.reason = reason ?? null
414
+ waitingHandler.done = true
415
+ waitingHandler.pendingTrailers = null
416
+ waitingHandler.bufferedChunks = []
417
+ waitingHandler.bufferedBytes = 0
418
+ }
419
+ }
420
+
421
+ return waitingHandler
422
+ }
423
+
424
+ /**
425
+ * @param {WaitingHandler} waitingHandler
426
+ * @param {Buffer} chunk
427
+ */
428
+ #bufferWaitingChunk (waitingHandler, chunk) {
429
+ if (waitingHandler.done || waitingHandler.controller.aborted) {
430
+ waitingHandler.done = true
431
+ waitingHandler.bufferedChunks = []
432
+ waitingHandler.bufferedBytes = 0
433
+ return
434
+ }
435
+
436
+ const bufferedChunk = Buffer.from(chunk)
437
+ waitingHandler.bufferedChunks.push(bufferedChunk)
438
+ waitingHandler.bufferedBytes += bufferedChunk.length
439
+
440
+ if (waitingHandler.bufferedBytes > this.#maxBufferSize) {
441
+ const err = new RequestAbortedError(`Deduplicated waiting handler exceeded maxBufferSize (${this.#maxBufferSize} bytes) while paused`)
442
+ this.#errorWaitingHandler(waitingHandler, err)
443
+ }
444
+ }
445
+
446
+ /**
447
+ * @param {WaitingHandler} waitingHandler
448
+ */
449
+ #flushWaitingHandler (waitingHandler) {
450
+ const { handler, controller } = waitingHandler
451
+
452
+ while (
453
+ !waitingHandler.done &&
454
+ !controller.aborted &&
455
+ !controller.paused &&
456
+ waitingHandler.bufferedChunks.length > 0
457
+ ) {
458
+ const bufferedChunk = waitingHandler.bufferedChunks.shift()
459
+ waitingHandler.bufferedBytes -= bufferedChunk.length
460
+
461
+ try {
462
+ handler.onResponseData?.(controller, bufferedChunk)
463
+ } catch {
464
+ // Ignore errors from waiting handlers
465
+ }
466
+
467
+ if (controller.aborted) {
468
+ waitingHandler.done = true
469
+ waitingHandler.pendingTrailers = null
470
+ waitingHandler.bufferedChunks = []
471
+ waitingHandler.bufferedBytes = 0
472
+ break
473
+ }
474
+ }
475
+ }
476
+
477
+ /**
478
+ * @param {WaitingHandler} waitingHandler
479
+ * @param {Error} err
480
+ */
481
+ #errorWaitingHandler (waitingHandler, err) {
482
+ if (waitingHandler.done) {
483
+ return
484
+ }
485
+
486
+ waitingHandler.done = true
487
+ waitingHandler.pendingTrailers = null
488
+ waitingHandler.bufferedChunks = []
489
+ waitingHandler.bufferedBytes = 0
490
+
491
+ try {
492
+ waitingHandler.controller.abort(err)
493
+ waitingHandler.handler.onResponseError?.(waitingHandler.controller, err)
494
+ } catch {
495
+ // Ignore errors from waiting handlers
496
+ }
497
+ }
498
+
499
+ #pruneDoneWaitingHandlers () {
500
+ this.#waitingHandlers = this.#waitingHandlers.filter(waitingHandler => waitingHandler.done === false)
501
+ }
502
+ }
503
+
504
+ module.exports = DeduplicationHandler