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,1056 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { pipeline } = require('node:stream')
5
+ const util = require('../core/util.js')
6
+ const {
7
+ RequestContentLengthMismatchError,
8
+ RequestAbortedError,
9
+ SocketError,
10
+ InformationalError,
11
+ InvalidArgumentError,
12
+ HeadersTimeoutError,
13
+ BodyTimeoutError
14
+ } = require('../core/errors.js')
15
+ const {
16
+ kUrl,
17
+ kReset,
18
+ kClient,
19
+ kRunning,
20
+ kPending,
21
+ kQueue,
22
+ kPendingIdx,
23
+ kRunningIdx,
24
+ kError,
25
+ kSocket,
26
+ kStrictContentLength,
27
+ kOnError,
28
+ kMaxConcurrentStreams,
29
+ kPingInterval,
30
+ kHTTP2Session,
31
+ kHTTP2InitialWindowSize,
32
+ kHTTP2ConnectionWindowSize,
33
+ kResume,
34
+ kSize,
35
+ kHTTPContext,
36
+ kClosed,
37
+ kBodyTimeout,
38
+ kHeadersTimeout,
39
+ kEnableConnectProtocol,
40
+ kRemoteSettings,
41
+ kHTTP2Stream,
42
+ kHTTP2SessionState
43
+ } = require('../core/symbols.js')
44
+ const { channels } = require('../core/diagnostics.js')
45
+
46
+ const kOpenStreams = Symbol('open streams')
47
+
48
+ let extractBody
49
+
50
+ /** @type {import('http2')} */
51
+ let http2
52
+ try {
53
+ http2 = require('node:http2')
54
+ } catch {
55
+ // @ts-ignore
56
+ http2 = { constants: {} }
57
+ }
58
+
59
+ const {
60
+ constants: {
61
+ HTTP2_HEADER_AUTHORITY,
62
+ HTTP2_HEADER_METHOD,
63
+ HTTP2_HEADER_PATH,
64
+ HTTP2_HEADER_SCHEME,
65
+ HTTP2_HEADER_CONTENT_LENGTH,
66
+ HTTP2_HEADER_EXPECT,
67
+ HTTP2_HEADER_STATUS,
68
+ HTTP2_HEADER_PROTOCOL,
69
+ NGHTTP2_REFUSED_STREAM,
70
+ NGHTTP2_CANCEL
71
+ }
72
+ } = http2
73
+
74
+ function parseH2Headers (headers) {
75
+ const result = []
76
+
77
+ for (const [name, value] of Object.entries(headers)) {
78
+ // h2 may concat the header value by array
79
+ // e.g. Set-Cookie
80
+ if (Array.isArray(value)) {
81
+ for (const subvalue of value) {
82
+ // we need to provide each header value of header name
83
+ // because the headers handler expect name-value pair
84
+ result.push(Buffer.from(name), Buffer.from(subvalue))
85
+ }
86
+ } else {
87
+ result.push(Buffer.from(name), Buffer.from(value))
88
+ }
89
+ }
90
+
91
+ return result
92
+ }
93
+
94
+ function connectH2 (client, socket) {
95
+ client[kSocket] = socket
96
+
97
+ const http2InitialWindowSize = client[kHTTP2InitialWindowSize]
98
+ const http2ConnectionWindowSize = client[kHTTP2ConnectionWindowSize]
99
+
100
+ const session = http2.connect(client[kUrl], {
101
+ createConnection: () => socket,
102
+ peerMaxConcurrentStreams: client[kMaxConcurrentStreams],
103
+ settings: {
104
+ // TODO(metcoder95): add support for PUSH
105
+ enablePush: false,
106
+ ...(http2InitialWindowSize != null ? { initialWindowSize: http2InitialWindowSize } : null)
107
+ }
108
+ })
109
+
110
+ client[kSocket] = socket
111
+ session[kOpenStreams] = 0
112
+ session[kClient] = client
113
+ session[kSocket] = socket
114
+ session[kHTTP2SessionState] = {
115
+ ping: {
116
+ interval: client[kPingInterval] === 0 ? null : setInterval(onHttp2SendPing, client[kPingInterval], session).unref()
117
+ }
118
+ }
119
+ // We set it to true by default in a best-effort; however once connected to an H2 server
120
+ // we will check if extended CONNECT protocol is supported or not
121
+ // and set this value accordingly.
122
+ session[kEnableConnectProtocol] = false
123
+ // States whether or not we have received the remote settings from the server
124
+ session[kRemoteSettings] = false
125
+
126
+ // Apply connection-level flow control once connected (if supported).
127
+ if (http2ConnectionWindowSize) {
128
+ util.addListener(session, 'connect', applyConnectionWindowSize.bind(session, http2ConnectionWindowSize))
129
+ }
130
+
131
+ util.addListener(session, 'error', onHttp2SessionError)
132
+ util.addListener(session, 'frameError', onHttp2FrameError)
133
+ util.addListener(session, 'end', onHttp2SessionEnd)
134
+ util.addListener(session, 'goaway', onHttp2SessionGoAway)
135
+ util.addListener(session, 'close', onHttp2SessionClose)
136
+ util.addListener(session, 'remoteSettings', onHttp2RemoteSettings)
137
+ // TODO (@metcoder95): implement SETTINGS support
138
+ // util.addListener(session, 'localSettings', onHttp2RemoteSettings)
139
+
140
+ session.unref()
141
+
142
+ client[kHTTP2Session] = session
143
+ socket[kHTTP2Session] = session
144
+
145
+ util.addListener(socket, 'error', onHttp2SocketError)
146
+ util.addListener(socket, 'end', onHttp2SocketEnd)
147
+ util.addListener(socket, 'close', onHttp2SocketClose)
148
+
149
+ socket[kClosed] = false
150
+ socket.on('close', onSocketClose)
151
+
152
+ return {
153
+ version: 'h2',
154
+ defaultPipelining: Infinity,
155
+ /**
156
+ * @param {import('../core/request.js')} request
157
+ * @returns {boolean}
158
+ */
159
+ write (request) {
160
+ return writeH2(client, request)
161
+ },
162
+ /**
163
+ * @returns {void}
164
+ */
165
+ resume () {
166
+ resumeH2(client)
167
+ },
168
+ /**
169
+ * @param {Error | null} err
170
+ * @param {() => void} callback
171
+ */
172
+ destroy (err, callback) {
173
+ if (socket[kClosed]) {
174
+ queueMicrotask(callback)
175
+ } else {
176
+ socket.destroy(err).on('close', callback)
177
+ }
178
+ },
179
+ /**
180
+ * @type {boolean}
181
+ */
182
+ get destroyed () {
183
+ return socket.destroyed
184
+ },
185
+ /**
186
+ * @param {import('../core/request.js')} request
187
+ * @returns {boolean}
188
+ */
189
+ busy (request) {
190
+ if (request != null) {
191
+ if (client[kRunning] > 0) {
192
+ // We are already processing requests
193
+
194
+ // Non-idempotent request cannot be retried.
195
+ // Ensure that no other requests are inflight and
196
+ // could cause failure.
197
+ if (request.idempotent === false) return true
198
+ // Don't dispatch an upgrade until all preceding requests have completed.
199
+ // Possibly, we do not have remote settings confirmed yet.
200
+ if ((request.upgrade === 'websocket' || request.method === 'CONNECT') && session[kRemoteSettings] === false) return true
201
+ // Request with stream or iterator body can error while other requests
202
+ // are inflight and indirectly error those as well.
203
+ // Ensure this doesn't happen by waiting for inflight
204
+ // to complete before dispatching.
205
+
206
+ // Request with stream or iterator body cannot be retried.
207
+ // Ensure that no other requests are inflight and
208
+ // could cause failure.
209
+ if (util.bodyLength(request.body) !== 0 &&
210
+ (util.isStream(request.body) || util.isAsyncIterable(request.body) || util.isFormDataLike(request.body))) return true
211
+ } else {
212
+ return (request.upgrade === 'websocket' || request.method === 'CONNECT') && session[kRemoteSettings] === false
213
+ }
214
+ }
215
+
216
+ return false
217
+ }
218
+ }
219
+ }
220
+
221
+ function resumeH2 (client) {
222
+ const socket = client[kSocket]
223
+
224
+ if (socket?.destroyed === false) {
225
+ // Only let the process exit when there is genuinely nothing outstanding.
226
+ // Unreffing because the peer advertised MAX_CONCURRENT_STREAMS = 0 left
227
+ // queued requests with nothing holding the event loop open, so the process
228
+ // could exit with status 0 while an awaited request never settled.
229
+ if (client[kSize] === 0) {
230
+ socket.unref()
231
+ client[kHTTP2Session].unref()
232
+ } else {
233
+ socket.ref()
234
+ client[kHTTP2Session].ref()
235
+ }
236
+ }
237
+ }
238
+
239
+ function applyConnectionWindowSize (connectionWindowSize) {
240
+ try {
241
+ if (typeof this.setLocalWindowSize === 'function') {
242
+ this.setLocalWindowSize(connectionWindowSize)
243
+ }
244
+ } catch {
245
+ // Best-effort only.
246
+ }
247
+ }
248
+
249
+ function onHttp2RemoteSettings (settings) {
250
+ // Fallbacks are a safe bet, remote setting will always override
251
+ this[kClient][kMaxConcurrentStreams] = settings.maxConcurrentStreams ?? this[kClient][kMaxConcurrentStreams]
252
+ /**
253
+ * From RFC-8441
254
+ * A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter
255
+ * with the value of 0 after previously sending a value of 1.
256
+ */
257
+ // Note: Cannot be tested in Node, it does not supports disabling the extended CONNECT protocol once enabled
258
+ if (this[kRemoteSettings] === true && this[kEnableConnectProtocol] === true && settings.enableConnectProtocol === false) {
259
+ const err = new InformationalError('HTTP/2: Server disabled extended CONNECT protocol against RFC-8441')
260
+ this[kSocket][kError] = err
261
+ this[kClient][kOnError](err)
262
+ return
263
+ }
264
+
265
+ this[kEnableConnectProtocol] = settings.enableConnectProtocol ?? this[kEnableConnectProtocol]
266
+ this[kRemoteSettings] = true
267
+ this[kClient][kResume]()
268
+ }
269
+
270
+ function onHttp2SendPing (session) {
271
+ const state = session[kHTTP2SessionState]
272
+ if ((session.closed || session.destroyed) && state.ping.interval != null) {
273
+ clearInterval(state.ping.interval)
274
+ state.ping.interval = null
275
+ return
276
+ }
277
+
278
+ // If no ping sent, do nothing
279
+ session.ping(onPing.bind(session))
280
+
281
+ function onPing (err, duration) {
282
+ const client = this[kClient]
283
+ const socket = this[kClient]
284
+
285
+ if (err != null) {
286
+ const error = new InformationalError(`HTTP/2: "PING" errored - type ${err.message}`)
287
+ socket[kError] = error
288
+ client[kOnError](error)
289
+ } else {
290
+ client.emit('ping', duration)
291
+ }
292
+ }
293
+ }
294
+
295
+ function onHttp2SessionError (err) {
296
+ this[kSocket][kError] = err
297
+ this[kClient][kOnError](err)
298
+ }
299
+
300
+ function onHttp2FrameError (type, code, id) {
301
+ if (id === 0) {
302
+ const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
303
+ this[kSocket][kError] = err
304
+ this[kClient][kOnError](err)
305
+ }
306
+ }
307
+
308
+ function onHttp2SessionEnd () {
309
+ const err = new SocketError('other side closed', util.getSocketInfo(this[kSocket]))
310
+ this.destroy(err)
311
+ util.destroy(this[kSocket], err)
312
+ }
313
+
314
+ /**
315
+ * This is the root cause of #3011
316
+ * We need to handle GOAWAY frames properly, and trigger the session close
317
+ * along with the socket right away
318
+ *
319
+ * @this {import('http2').ClientHttp2Session}
320
+ * @param {number} errorCode
321
+ */
322
+ // Backport of #5410 and #5569. HTTP/2 multiplexes, so requests complete out of
323
+ // order; advancing kRunningIdx blindly retired whichever request happened to
324
+ // sit at the head instead of the one that actually finished, which both lost
325
+ // requests and left phantom running slots behind.
326
+ function completeRequest (client, request, resetPendingIdx = false) {
327
+ const queue = client[kQueue]
328
+ const runningIdx = client[kRunningIdx]
329
+
330
+ // In-order completion: clear the request and advance without splicing.
331
+ // The client's resume loop compacts cleared slots once the index grows.
332
+ if (runningIdx < client[kPendingIdx] && queue[runningIdx] === request) {
333
+ queue[runningIdx] = null
334
+ client[kRunningIdx] = runningIdx + 1
335
+ return
336
+ }
337
+
338
+ const index = queue.indexOf(request, runningIdx)
339
+
340
+ if (index === -1 || index >= client[kPendingIdx]) {
341
+ return
342
+ }
343
+
344
+ queue.splice(index, 1)
345
+ client[kPendingIdx]--
346
+
347
+ if (resetPendingIdx && client[kPendingIdx] < client[kRunningIdx]) {
348
+ client[kPendingIdx] = client[kRunningIdx]
349
+ }
350
+ }
351
+
352
+ function onHttp2SessionGoAway (errorCode) {
353
+ // TODO(mcollina): Verify if GOAWAY implements the spec correctly:
354
+ // https://datatracker.ietf.org/doc/html/rfc7540#section-6.8
355
+ // Specifically, we do not verify the "valid" stream id.
356
+
357
+ const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`, util.getSocketInfo(this[kSocket]))
358
+ const client = this[kClient]
359
+
360
+ client[kSocket] = null
361
+ client[kHTTPContext] = null
362
+
363
+ // this is an HTTP2 session
364
+ this.close()
365
+ this[kHTTP2Session] = null
366
+
367
+ util.destroy(this[kSocket], err)
368
+
369
+ // Fail head of pipeline.
370
+ if (client[kRunningIdx] < client[kQueue].length) {
371
+ const request = client[kQueue][client[kRunningIdx]]
372
+ client[kQueue][client[kRunningIdx]++] = null
373
+ if (request != null) {
374
+ util.errorRequest(client, request, err)
375
+ }
376
+ client[kPendingIdx] = client[kRunningIdx]
377
+ }
378
+
379
+ assert(client[kRunning] === 0)
380
+
381
+ client.emit('disconnect', client[kUrl], [client], err)
382
+ client.emit('connectionError', client[kUrl], [client], err)
383
+
384
+ client[kResume]()
385
+ }
386
+
387
+ function onHttp2SessionClose () {
388
+ const { [kClient]: client, [kHTTP2SessionState]: state } = this
389
+ const { [kSocket]: socket } = client
390
+
391
+ const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
392
+
393
+ client[kSocket] = null
394
+ client[kHTTPContext] = null
395
+
396
+ if (state.ping.interval != null) {
397
+ clearInterval(state.ping.interval)
398
+ state.ping.interval = null
399
+ }
400
+
401
+ if (client.destroyed) {
402
+ assert(client[kPending] === 0)
403
+
404
+ // Fail entire queue.
405
+ const requests = client[kQueue].splice(client[kRunningIdx])
406
+ for (let i = 0; i < requests.length; i++) {
407
+ const request = requests[i]
408
+ if (request != null) {
409
+ util.errorRequest(client, request, err)
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ function onHttp2SocketClose () {
416
+ const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
417
+
418
+ const client = this[kHTTP2Session][kClient]
419
+
420
+ client[kSocket] = null
421
+ client[kHTTPContext] = null
422
+
423
+ if (this[kHTTP2Session] !== null) {
424
+ this[kHTTP2Session].destroy(err)
425
+ }
426
+
427
+ client[kPendingIdx] = client[kRunningIdx]
428
+
429
+ assert(client[kRunning] === 0)
430
+
431
+ client.emit('disconnect', client[kUrl], [client], err)
432
+
433
+ client[kResume]()
434
+ }
435
+
436
+ function onHttp2SocketError (err) {
437
+ this[kError] = err
438
+
439
+ this[kClient][kOnError](err)
440
+ }
441
+
442
+ function onHttp2SocketEnd () {
443
+ util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
444
+ }
445
+
446
+ function onSocketClose () {
447
+ this[kClosed] = true
448
+ }
449
+
450
+ // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
451
+ function shouldSendContentLength (method) {
452
+ return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
453
+ }
454
+
455
+ function writeH2 (client, request) {
456
+ // Time to the response headers, then time between body chunks. Using
457
+ // bodyTimeout for both made headersTimeout a no-op over HTTP/2.
458
+ const headersTimeout = request.headersTimeout ?? client[kHeadersTimeout]
459
+ const bodyTimeout = request.bodyTimeout ?? client[kBodyTimeout]
460
+ const session = client[kHTTP2Session]
461
+ const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
462
+ let { body } = request
463
+
464
+ if (upgrade != null && upgrade !== 'websocket') {
465
+ util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`))
466
+ return false
467
+ }
468
+
469
+ const headers = {}
470
+ for (let n = 0; n < reqHeaders.length; n += 2) {
471
+ const key = reqHeaders[n + 0]
472
+ const val = reqHeaders[n + 1]
473
+
474
+ if (key === 'cookie') {
475
+ if (headers[key] != null) {
476
+ headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val]
477
+ } else {
478
+ headers[key] = val
479
+ }
480
+
481
+ continue
482
+ }
483
+
484
+ if (Array.isArray(val)) {
485
+ for (let i = 0; i < val.length; i++) {
486
+ if (headers[key]) {
487
+ headers[key] += `, ${val[i]}`
488
+ } else {
489
+ headers[key] = val[i]
490
+ }
491
+ }
492
+ } else if (headers[key]) {
493
+ headers[key] += `, ${val}`
494
+ } else {
495
+ headers[key] = val
496
+ }
497
+ }
498
+
499
+ /** @type {import('node:http2').ClientHttp2Stream} */
500
+ let stream = null
501
+
502
+ const { hostname, port } = client[kUrl]
503
+
504
+ headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
505
+ headers[HTTP2_HEADER_METHOD] = method
506
+
507
+ const abort = (err) => {
508
+ if (request.aborted || request.completed) {
509
+ return
510
+ }
511
+
512
+ err = err || new RequestAbortedError()
513
+
514
+ util.errorRequest(client, request, err)
515
+
516
+ if (stream != null) {
517
+ // Some chunks might still come after abort,
518
+ // let's ignore them
519
+ stream.removeAllListeners('data')
520
+
521
+ // On Abort, we close the stream to send RST_STREAM frame
522
+ stream.close()
523
+
524
+ // We move the running index to the next request
525
+ client[kOnError](err)
526
+ completeRequest(client, request)
527
+ client[kResume]()
528
+ }
529
+
530
+ // We do not destroy the socket as we can continue using the session
531
+ // the stream gets destroyed and the session remains to create new streams
532
+ util.destroy(body, err)
533
+ }
534
+
535
+ try {
536
+ // We are already connected, streams are pending.
537
+ // We can call on connect, and wait for abort
538
+ request.onConnect(abort)
539
+ } catch (err) {
540
+ util.errorRequest(client, request, err)
541
+ }
542
+
543
+ if (request.aborted) {
544
+ return false
545
+ }
546
+
547
+ if (upgrade || method === 'CONNECT') {
548
+ session.ref()
549
+
550
+ if (upgrade === 'websocket') {
551
+ // We cannot upgrade to websocket if extended CONNECT protocol is not supported
552
+ if (session[kEnableConnectProtocol] === false) {
553
+ util.errorRequest(client, request, new InformationalError('HTTP/2: Extended CONNECT protocol not supported by server'))
554
+ session.unref()
555
+ return false
556
+ }
557
+
558
+ // We force the method to CONNECT
559
+ // as per RFC-8441
560
+ // https://datatracker.ietf.org/doc/html/rfc8441#section-4
561
+ headers[HTTP2_HEADER_METHOD] = 'CONNECT'
562
+ headers[HTTP2_HEADER_PROTOCOL] = 'websocket'
563
+ // :path and :scheme headers must be omitted when sending CONNECT but set if extended-CONNECT
564
+ headers[HTTP2_HEADER_PATH] = path
565
+
566
+ if (protocol === 'ws:' || protocol === 'wss:') {
567
+ headers[HTTP2_HEADER_SCHEME] = protocol === 'ws:' ? 'http' : 'https'
568
+ } else {
569
+ headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
570
+ }
571
+
572
+ stream = session.request(headers, { endStream: false, signal })
573
+ stream[kHTTP2Stream] = true
574
+ ++session[kOpenStreams]
575
+
576
+ stream.once('response', (headers, _flags) => {
577
+ const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
578
+
579
+ try {
580
+ request.onUpgrade(statusCode, parseH2Headers(realHeaders), stream)
581
+ } catch (err) {
582
+ abort(err)
583
+ return
584
+ }
585
+ completeRequest(client, request)
586
+ })
587
+
588
+ stream.on('error', () => {
589
+ if (stream.rstCode === NGHTTP2_REFUSED_STREAM || stream.rstCode === NGHTTP2_CANCEL) {
590
+ // NGHTTP2_REFUSED_STREAM (7) or NGHTTP2_CANCEL (8)
591
+ // We do not treat those as errors as the server might
592
+ // not support websockets and refuse the stream
593
+ abort(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
594
+ }
595
+ })
596
+
597
+ stream.once('close', () => {
598
+ session[kOpenStreams] -= 1
599
+ if (session[kOpenStreams] === 0) session.unref()
600
+ })
601
+
602
+ stream.setTimeout(headersTimeout)
603
+ return true
604
+ }
605
+
606
+ // TODO: consolidate once we support CONNECT properly
607
+ // NOTE: We are already connected, streams are pending, first request
608
+ // will create a new stream. We trigger a request to create the stream and wait until
609
+ // `ready` event is triggered
610
+ // We disabled endStream to allow the user to write to the stream
611
+ stream = session.request(headers, { endStream: false, signal })
612
+ stream[kHTTP2Stream] = true
613
+ ++session[kOpenStreams]
614
+ stream.on('response', headers => {
615
+ const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
616
+
617
+ try {
618
+ request.onUpgrade(statusCode, parseH2Headers(realHeaders), stream)
619
+ } catch (err) {
620
+ abort(err)
621
+ return
622
+ }
623
+ completeRequest(client, request)
624
+ })
625
+ stream.on('error', abort)
626
+ stream.once('close', () => {
627
+ session[kOpenStreams] -= 1
628
+ if (session[kOpenStreams] === 0) session.unref()
629
+ })
630
+ stream.setTimeout(headersTimeout)
631
+
632
+ return true
633
+ }
634
+
635
+ // https://tools.ietf.org/html/rfc7540#section-8.3
636
+ // :path and :scheme headers must be omitted when sending CONNECT
637
+ headers[HTTP2_HEADER_PATH] = path
638
+ headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
639
+
640
+ // https://tools.ietf.org/html/rfc7231#section-4.3.1
641
+ // https://tools.ietf.org/html/rfc7231#section-4.3.2
642
+ // https://tools.ietf.org/html/rfc7231#section-4.3.5
643
+
644
+ // Sending a payload body on a request that does not
645
+ // expect it can cause undefined behavior on some
646
+ // servers and corrupt connection state. Do not
647
+ // re-use the connection for further requests.
648
+
649
+ const expectsPayload = (
650
+ method === 'PUT' ||
651
+ method === 'POST' ||
652
+ method === 'PATCH'
653
+ )
654
+
655
+ if (body && typeof body.read === 'function') {
656
+ // Try to read EOF in order to get length.
657
+ body.read(0)
658
+ }
659
+
660
+ let contentLength = util.bodyLength(body)
661
+
662
+ if (util.isFormDataLike(body)) {
663
+ extractBody ??= require('../web/fetch/body.js').extractBody
664
+
665
+ const [bodyStream, contentType] = extractBody(body)
666
+ headers['content-type'] = contentType
667
+
668
+ body = bodyStream.stream
669
+ contentLength = bodyStream.length
670
+ }
671
+
672
+ if (contentLength == null) {
673
+ contentLength = request.contentLength
674
+ }
675
+
676
+ if (!expectsPayload) {
677
+ // https://tools.ietf.org/html/rfc7230#section-3.3.2
678
+ // A user agent SHOULD NOT send a Content-Length header field when
679
+ // the request message does not contain a payload body and the method
680
+ // semantics do not anticipate such a body.
681
+ // And for methods that don't expect a payload, omit Content-Length.
682
+ contentLength = null
683
+ }
684
+
685
+ // https://github.com/nodejs/undici/issues/2046
686
+ // A user agent may send a Content-Length header with 0 value, this should be allowed.
687
+ if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) {
688
+ if (client[kStrictContentLength]) {
689
+ util.errorRequest(client, request, new RequestContentLengthMismatchError())
690
+ return false
691
+ }
692
+
693
+ process.emitWarning(new RequestContentLengthMismatchError())
694
+ }
695
+
696
+ if (contentLength != null) {
697
+ assert(body || contentLength === 0, 'no body must not have content length')
698
+ headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}`
699
+ }
700
+
701
+ session.ref()
702
+
703
+ if (channels.sendHeaders.hasSubscribers) {
704
+ let header = ''
705
+ for (const key in headers) {
706
+ header += `${key}: ${headers[key]}\r\n`
707
+ }
708
+ channels.sendHeaders.publish({ request, headers: header, socket: session[kSocket] })
709
+ }
710
+
711
+ // TODO(metcoder95): add support for sending trailers
712
+ const shouldEndStream = method === 'GET' || method === 'HEAD' || body === null
713
+ if (expectContinue) {
714
+ headers[HTTP2_HEADER_EXPECT] = '100-continue'
715
+ stream = session.request(headers, { endStream: shouldEndStream, signal })
716
+ stream[kHTTP2Stream] = true
717
+
718
+ stream.once('continue', writeBodyH2)
719
+ } else {
720
+ stream = session.request(headers, {
721
+ endStream: shouldEndStream,
722
+ signal
723
+ })
724
+ stream[kHTTP2Stream] = true
725
+
726
+ writeBodyH2()
727
+ }
728
+
729
+ // Increment counter as we have new streams open
730
+ ++session[kOpenStreams]
731
+ stream.setTimeout(headersTimeout)
732
+
733
+ // Track whether we received a response (headers)
734
+ let responseReceived = false
735
+
736
+ stream.once('response', headers => {
737
+ const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
738
+ request.onResponseStarted()
739
+ responseReceived = true
740
+ stream.setTimeout(bodyTimeout)
741
+
742
+ // Due to the stream nature, it is possible we face a race condition
743
+ // where the stream has been assigned, but the request has been aborted
744
+ // the request remains in-flight and headers hasn't been received yet
745
+ // for those scenarios, best effort is to destroy the stream immediately
746
+ // as there's no value to keep it open.
747
+ if (request.aborted) {
748
+ stream.removeAllListeners('data')
749
+ return
750
+ }
751
+
752
+ if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) {
753
+ stream.pause()
754
+ }
755
+
756
+ stream.on('data', (chunk) => {
757
+ if (request.aborted || request.completed) {
758
+ return
759
+ }
760
+
761
+ if (request.onData(chunk) === false) {
762
+ stream.pause()
763
+ }
764
+ })
765
+ })
766
+
767
+ stream.once('end', () => {
768
+ stream.removeAllListeners('data')
769
+ // If we received a response, this is a normal completion
770
+ if (responseReceived) {
771
+ if (!request.aborted && !request.completed) {
772
+ request.onComplete({})
773
+ }
774
+
775
+ completeRequest(client, request)
776
+ client[kResume]()
777
+ } else {
778
+ // Stream ended without receiving a response - this is an error
779
+ // (e.g., server destroyed the stream before sending headers)
780
+ abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
781
+ completeRequest(client, request, true)
782
+ client[kResume]()
783
+ }
784
+ })
785
+
786
+ stream.once('close', () => {
787
+ stream.removeAllListeners('data')
788
+ session[kOpenStreams] -= 1
789
+ if (session[kOpenStreams] === 0) {
790
+ session.unref()
791
+ }
792
+
793
+ // A stream can close without ever emitting 'end' or 'error': a peer's
794
+ // RST_STREAM(CANCEL) received before the response is reported by Node as a
795
+ // bare 'close', and destroying the stream unenrolls its timeout, so no
796
+ // 'timeout' follows either. Nothing else would ever settle this request.
797
+ if (!request.aborted && !request.completed) {
798
+ abort(new InformationalError('HTTP/2: stream closed before the response was complete'))
799
+ }
800
+ })
801
+
802
+ stream.once('error', function (err) {
803
+ stream.removeAllListeners('data')
804
+ abort(err)
805
+ })
806
+
807
+ stream.once('frameError', (type, code) => {
808
+ stream.removeAllListeners('data')
809
+ abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
810
+ })
811
+
812
+ stream.on('aborted', () => {
813
+ stream.removeAllListeners('data')
814
+ })
815
+
816
+ stream.on('timeout', () => {
817
+ const err = responseReceived
818
+ ? new BodyTimeoutError(`HTTP/2: "body timeout after ${bodyTimeout}"`)
819
+ : new HeadersTimeoutError(`HTTP/2: "headers timeout after ${headersTimeout}"`)
820
+ stream.removeAllListeners('data')
821
+ session[kOpenStreams] -= 1
822
+
823
+ if (session[kOpenStreams] === 0) {
824
+ session.unref()
825
+ }
826
+
827
+ abort(err)
828
+ })
829
+
830
+ stream.once('trailers', trailers => {
831
+ if (request.aborted || request.completed) {
832
+ return
833
+ }
834
+
835
+ stream.removeAllListeners('data')
836
+ request.onComplete(trailers)
837
+ })
838
+
839
+ return true
840
+
841
+ function writeBodyH2 () {
842
+ if (!body || contentLength === 0) {
843
+ writeBuffer(
844
+ abort,
845
+ stream,
846
+ null,
847
+ client,
848
+ request,
849
+ client[kSocket],
850
+ contentLength,
851
+ expectsPayload
852
+ )
853
+ } else if (util.isBuffer(body)) {
854
+ writeBuffer(
855
+ abort,
856
+ stream,
857
+ body,
858
+ client,
859
+ request,
860
+ client[kSocket],
861
+ contentLength,
862
+ expectsPayload
863
+ )
864
+ } else if (util.isBlobLike(body)) {
865
+ if (typeof body.stream === 'function') {
866
+ writeIterable(
867
+ abort,
868
+ stream,
869
+ body.stream(),
870
+ client,
871
+ request,
872
+ client[kSocket],
873
+ contentLength,
874
+ expectsPayload
875
+ )
876
+ } else {
877
+ writeBlob(
878
+ abort,
879
+ stream,
880
+ body,
881
+ client,
882
+ request,
883
+ client[kSocket],
884
+ contentLength,
885
+ expectsPayload
886
+ )
887
+ }
888
+ } else if (util.isStream(body)) {
889
+ writeStream(
890
+ abort,
891
+ client[kSocket],
892
+ expectsPayload,
893
+ stream,
894
+ body,
895
+ client,
896
+ request,
897
+ contentLength
898
+ )
899
+ } else if (util.isIterable(body)) {
900
+ writeIterable(
901
+ abort,
902
+ stream,
903
+ body,
904
+ client,
905
+ request,
906
+ client[kSocket],
907
+ contentLength,
908
+ expectsPayload
909
+ )
910
+ } else {
911
+ assert(false)
912
+ }
913
+ }
914
+ }
915
+
916
+ function writeBuffer (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
917
+ try {
918
+ if (body != null && util.isBuffer(body)) {
919
+ assert(contentLength === body.byteLength, 'buffer body must have content length')
920
+ h2stream.cork()
921
+ h2stream.write(body)
922
+ h2stream.uncork()
923
+ h2stream.end()
924
+
925
+ request.onBodySent(body)
926
+ }
927
+
928
+ if (!expectsPayload) {
929
+ socket[kReset] = true
930
+ }
931
+
932
+ request.onRequestSent()
933
+ client[kResume]()
934
+ } catch (error) {
935
+ abort(error)
936
+ }
937
+ }
938
+
939
+ function writeStream (abort, socket, expectsPayload, h2stream, body, client, request, contentLength) {
940
+ assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
941
+
942
+ // For HTTP/2, is enough to pipe the stream
943
+ const pipe = pipeline(
944
+ body,
945
+ h2stream,
946
+ (err) => {
947
+ if (err) {
948
+ util.destroy(pipe, err)
949
+ abort(err)
950
+ } else {
951
+ util.removeAllListeners(pipe)
952
+ request.onRequestSent()
953
+
954
+ if (!expectsPayload) {
955
+ socket[kReset] = true
956
+ }
957
+
958
+ client[kResume]()
959
+ }
960
+ }
961
+ )
962
+
963
+ util.addListener(pipe, 'data', onPipeData)
964
+
965
+ function onPipeData (chunk) {
966
+ request.onBodySent(chunk)
967
+ }
968
+ }
969
+
970
+ async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
971
+ assert(contentLength === body.size, 'blob body must have content length')
972
+
973
+ try {
974
+ if (contentLength != null && contentLength !== body.size) {
975
+ throw new RequestContentLengthMismatchError()
976
+ }
977
+
978
+ const buffer = Buffer.from(await body.arrayBuffer())
979
+
980
+ h2stream.cork()
981
+ h2stream.write(buffer)
982
+ h2stream.uncork()
983
+ h2stream.end()
984
+
985
+ request.onBodySent(buffer)
986
+ request.onRequestSent()
987
+
988
+ if (!expectsPayload) {
989
+ socket[kReset] = true
990
+ }
991
+
992
+ client[kResume]()
993
+ } catch (err) {
994
+ abort(err)
995
+ }
996
+ }
997
+
998
+ async function writeIterable (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
999
+ assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
1000
+
1001
+ let callback = null
1002
+ function onDrain () {
1003
+ if (callback) {
1004
+ const cb = callback
1005
+ callback = null
1006
+ cb()
1007
+ }
1008
+ }
1009
+
1010
+ const waitForDrain = () => new Promise((resolve, reject) => {
1011
+ assert(callback === null)
1012
+
1013
+ if (socket[kError]) {
1014
+ reject(socket[kError])
1015
+ } else {
1016
+ callback = resolve
1017
+ }
1018
+ })
1019
+
1020
+ h2stream
1021
+ .on('close', onDrain)
1022
+ .on('drain', onDrain)
1023
+
1024
+ try {
1025
+ // It's up to the user to somehow abort the async iterable.
1026
+ for await (const chunk of body) {
1027
+ if (socket[kError]) {
1028
+ throw socket[kError]
1029
+ }
1030
+
1031
+ const res = h2stream.write(chunk)
1032
+ request.onBodySent(chunk)
1033
+ if (!res) {
1034
+ await waitForDrain()
1035
+ }
1036
+ }
1037
+
1038
+ h2stream.end()
1039
+
1040
+ request.onRequestSent()
1041
+
1042
+ if (!expectsPayload) {
1043
+ socket[kReset] = true
1044
+ }
1045
+
1046
+ client[kResume]()
1047
+ } catch (err) {
1048
+ abort(err)
1049
+ } finally {
1050
+ h2stream
1051
+ .off('close', onDrain)
1052
+ .off('drain', onDrain)
1053
+ }
1054
+ }
1055
+
1056
+ module.exports = connectH2