undici 8.0.2 → 8.0.3
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.
- package/docs/docs/api/Dispatcher.md +2 -2
- package/docs/docs/best-practices/migrating-from-v7-to-v8.md +231 -0
- package/index.js +2 -2
- package/lib/core/util.js +1 -5
- package/lib/dispatcher/balanced-pool.js +0 -3
- package/lib/dispatcher/dispatcher-base.js +4 -0
- package/lib/dispatcher/dispatcher1-wrapper.js +6 -0
- package/lib/dispatcher/h2c-client.js +1 -1
- package/lib/dispatcher/pool.js +0 -3
- package/lib/dispatcher/proxy-agent.js +4 -4
- package/lib/dispatcher/round-robin-pool.js +0 -3
- package/lib/dispatcher/socks5-proxy-agent.js +68 -62
- package/lib/handler/redirect-handler.js +1 -51
- package/lib/interceptor/decompress.js +1 -2
- package/lib/interceptor/dns.js +1 -1
- package/lib/util/cache.js +7 -6
- package/lib/util/runtime-features.js +3 -34
- package/lib/web/cache/cache.js +6 -8
- package/lib/web/fetch/body.js +1 -2
- package/lib/web/fetch/index.js +17 -9
- package/lib/web/fetch/util.js +4 -2
- package/lib/web/webidl/index.js +2 -4
- package/lib/web/websocket/stream/websocketstream.js +5 -6
- package/package.json +3 -3
- package/types/agent.d.ts +0 -2
- package/types/client.d.ts +7 -12
- package/types/dispatcher.d.ts +0 -2
- package/types/h2c-client.d.ts +6 -6
- package/types/pool.d.ts +0 -2
- package/types/round-robin-pool.d.ts +0 -2
- package/types/webidl.d.ts +0 -1
- package/lib/util/promise.js +0 -28
|
@@ -533,7 +533,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
533
533
|
|
|
534
534
|
`body` contains the following additional extensions:
|
|
535
535
|
|
|
536
|
-
- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default:
|
|
536
|
+
- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 131072.
|
|
537
537
|
|
|
538
538
|
Note that body will still be a `Readable` even if it is empty, but attempting to deserialize it with `json()` will result in an exception. Recommended way to ensure there is a body to deserialize is to check if status code is not 204, and `content-type` header starts with `application/json`.
|
|
539
539
|
|
|
@@ -1031,7 +1031,7 @@ const client = new Client("http://service.example").compose(
|
|
|
1031
1031
|
The `dump` interceptor enables you to dump the response body from a request upon a given limit.
|
|
1032
1032
|
|
|
1033
1033
|
**Options**
|
|
1034
|
-
- `maxSize` - The maximum size (in bytes) of the response body to dump. If the size of the
|
|
1034
|
+
- `maxSize` - The maximum size (in bytes) of the response body to dump. If the size of the response's body exceeds this value then the connection will be closed. Default: `1048576`.
|
|
1035
1035
|
|
|
1036
1036
|
> The `Dispatcher#options` also gets extended with the options `dumpMaxSize`, `abortOnDumped`, and `waitForTrailers` which can be used to configure the interceptor at a request-per-request basis.
|
|
1037
1037
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Migrating from Undici 7 to 8
|
|
2
|
+
|
|
3
|
+
This guide covers the changes you are most likely to hit when upgrading an
|
|
4
|
+
application or library from Undici v7 to v8.
|
|
5
|
+
|
|
6
|
+
## Before you upgrade
|
|
7
|
+
|
|
8
|
+
- Make sure your runtime is Node.js `>= 22.19.0`.
|
|
9
|
+
- If you have custom dispatchers, interceptors, or handlers, review the
|
|
10
|
+
handler API changes before updating.
|
|
11
|
+
- If you rely on HTTP/1.1-only behavior, plan to set `allowH2: false`
|
|
12
|
+
explicitly.
|
|
13
|
+
|
|
14
|
+
## 1. Update your Node.js version
|
|
15
|
+
|
|
16
|
+
Undici v8 requires Node.js `>= 22.19.0`.
|
|
17
|
+
|
|
18
|
+
If you are still on Node.js 20 or an older Node.js 22 release, upgrade Node.js
|
|
19
|
+
first:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
node -v
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If that command prints a version lower than `v22.19.0`, upgrade Node.js before
|
|
26
|
+
installing Undici v8.
|
|
27
|
+
|
|
28
|
+
## 2. Migrate custom dispatcher handlers to the v2 API
|
|
29
|
+
|
|
30
|
+
Undici v8 uses the newer dispatcher handler API consistently.
|
|
31
|
+
|
|
32
|
+
If you implemented custom dispatchers, interceptors, or wrappers around
|
|
33
|
+
`dispatch()`, update legacy callbacks such as `onConnect`, `onHeaders`, and
|
|
34
|
+
`onComplete` to the newer callback names.
|
|
35
|
+
|
|
36
|
+
### Old handler callbacks vs. v8 callbacks
|
|
37
|
+
|
|
38
|
+
| Undici 7 style | Undici 8 style |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `onConnect(abort, context)` | `onRequestStart(controller, context)` |
|
|
41
|
+
| `onHeaders(statusCode, rawHeaders, resume, statusText)` | `onResponseStart(controller, statusCode, headers, statusText)` |
|
|
42
|
+
| `onData(chunk)` | `onResponseData(controller, chunk)` |
|
|
43
|
+
| `onComplete(trailers)` | `onResponseEnd(controller, trailers)` |
|
|
44
|
+
| `onError(err)` | `onResponseError(controller, err)` |
|
|
45
|
+
| `onUpgrade(statusCode, rawHeaders, socket)` | `onRequestUpgrade(controller, statusCode, headers, socket)` |
|
|
46
|
+
|
|
47
|
+
### Example
|
|
48
|
+
|
|
49
|
+
Before:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
client.dispatch(options, {
|
|
53
|
+
onConnect (abort) {
|
|
54
|
+
this.abort = abort
|
|
55
|
+
},
|
|
56
|
+
onHeaders (statusCode, headers, resume) {
|
|
57
|
+
this.resume = resume
|
|
58
|
+
return true
|
|
59
|
+
},
|
|
60
|
+
onData (chunk) {
|
|
61
|
+
chunks.push(chunk)
|
|
62
|
+
return true
|
|
63
|
+
},
|
|
64
|
+
onComplete (trailers) {
|
|
65
|
+
console.log(trailers)
|
|
66
|
+
},
|
|
67
|
+
onError (err) {
|
|
68
|
+
console.error(err)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
After:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
client.dispatch(options, {
|
|
77
|
+
onRequestStart (controller) {
|
|
78
|
+
this.controller = controller
|
|
79
|
+
},
|
|
80
|
+
onResponseStart (controller, statusCode, headers, statusText) {
|
|
81
|
+
console.log(statusCode, statusText, headers)
|
|
82
|
+
},
|
|
83
|
+
onResponseData (controller, chunk) {
|
|
84
|
+
chunks.push(chunk)
|
|
85
|
+
},
|
|
86
|
+
onResponseEnd (controller, trailers) {
|
|
87
|
+
console.log(trailers)
|
|
88
|
+
},
|
|
89
|
+
onResponseError (controller, err) {
|
|
90
|
+
console.error(err)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Pause, resume, and abort now go through the controller
|
|
96
|
+
|
|
97
|
+
In Undici v7, legacy handlers could return `false` or keep references to
|
|
98
|
+
`abort()` and `resume()` callbacks. In Undici v8, use the controller instead:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
onRequestStart (controller) {
|
|
102
|
+
this.controller = controller
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onResponseData (controller, chunk) {
|
|
106
|
+
controller.pause()
|
|
107
|
+
setImmediate(() => controller.resume())
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onResponseError (controller, err) {
|
|
111
|
+
controller.abort(err)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Raw headers and trailers moved to the controller
|
|
116
|
+
|
|
117
|
+
If you need the raw header arrays, read them from the controller:
|
|
118
|
+
|
|
119
|
+
- `controller.rawHeaders`
|
|
120
|
+
- `controller.rawTrailers`
|
|
121
|
+
|
|
122
|
+
## 3. Update `onBodySent()` handlers
|
|
123
|
+
|
|
124
|
+
If you implemented `onBodySent()`, note that its signature changed.
|
|
125
|
+
|
|
126
|
+
Before, handlers received counters:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
onBodySent (chunkSize, totalBytesSent) {}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
In Undici v8, handlers receive the actual chunk:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
onBodySent (chunk) {}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If you need a notification that the whole body has been sent, use
|
|
139
|
+
`onRequestSent()`:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
onRequestSent () {
|
|
143
|
+
console.log('request body fully sent')
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 4. If you need HTTP/1.1 only, disable HTTP/2 explicitly
|
|
148
|
+
|
|
149
|
+
Undici v8 enables HTTP/2 by default when a TLS server negotiates it via ALPN.
|
|
150
|
+
|
|
151
|
+
If your application depends on HTTP/1.1-specific behavior, set `allowH2: false`
|
|
152
|
+
explicitly.
|
|
153
|
+
|
|
154
|
+
Before:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
const client = new Client('https://example.com')
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
After, to keep HTTP/1.1 only:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
const client = new Client('https://example.com', {
|
|
164
|
+
allowH2: false
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The same applies when you configure an `Agent`:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
const agent = new Agent({
|
|
172
|
+
allowH2: false
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 5. Use real `Blob` and `File` instances
|
|
177
|
+
|
|
178
|
+
Undici v8 no longer accepts fake Blob-like values that only imitate `Blob` or
|
|
179
|
+
`File` via properties such as `Symbol.toStringTag`.
|
|
180
|
+
|
|
181
|
+
If you were passing custom objects that looked like `Blob`s, replace them with
|
|
182
|
+
actual `Blob` or `File` instances:
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
const body = new Blob(['hello'])
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 6. Avoid depending on the internal global dispatcher symbol
|
|
189
|
+
|
|
190
|
+
`setGlobalDispatcher()` and `getGlobalDispatcher()` remain the public APIs and
|
|
191
|
+
should continue to be used.
|
|
192
|
+
|
|
193
|
+
Internally, Undici v8 stores its dispatcher under
|
|
194
|
+
`Symbol.for('undici.globalDispatcher.2')` and mirrors a v1-compatible wrapper
|
|
195
|
+
for legacy consumers such as Node.js built-in `fetch`.
|
|
196
|
+
|
|
197
|
+
If your code was reading or writing `Symbol.for('undici.globalDispatcher.1')`
|
|
198
|
+
directly, migrate to the public APIs instead:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
import { setGlobalDispatcher, getGlobalDispatcher, Agent } from 'undici'
|
|
202
|
+
|
|
203
|
+
setGlobalDispatcher(new Agent())
|
|
204
|
+
const dispatcher = getGlobalDispatcher()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
If you must expose a dispatcher to legacy v1 handler consumers, wrap it with
|
|
208
|
+
`Dispatcher1Wrapper`:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
import { Agent, Dispatcher1Wrapper } from 'undici'
|
|
212
|
+
|
|
213
|
+
const legacyCompatibleDispatcher = new Dispatcher1Wrapper(new Agent())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## 7. Verify the upgrade
|
|
217
|
+
|
|
218
|
+
After moving to Undici v8, it is worth checking these paths in your test suite:
|
|
219
|
+
|
|
220
|
+
- requests that use a custom `dispatcher`
|
|
221
|
+
- `setGlobalDispatcher()` behavior
|
|
222
|
+
- any custom interceptor or retry handler
|
|
223
|
+
- uploads that use `Blob`, `File`, or `FormData`
|
|
224
|
+
- integrations that depend on HTTP/1.1-only behavior
|
|
225
|
+
|
|
226
|
+
## Related documentation
|
|
227
|
+
|
|
228
|
+
- [Dispatcher](/docs/api/Dispatcher.md)
|
|
229
|
+
- [Client](/docs/api/Client.md)
|
|
230
|
+
- [Global Installation](/docs/api/GlobalInstallation.md)
|
|
231
|
+
- [Undici Module vs. Node.js Built-in Fetch](/docs/best-practices/undici-vs-builtin-fetch.md)
|
package/index.js
CHANGED
|
@@ -105,14 +105,14 @@ function makeDispatcher (fn) {
|
|
|
105
105
|
url = util.parseURL(url)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const { agent, dispatcher = getGlobalDispatcher() } = opts
|
|
108
|
+
const { agent, dispatcher = getGlobalDispatcher(), ...restOpts } = opts
|
|
109
109
|
|
|
110
110
|
if (agent) {
|
|
111
111
|
throw new InvalidArgumentError('unsupported opts.agent. Did you mean opts.client?')
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
return fn.call(dispatcher, {
|
|
115
|
-
...
|
|
115
|
+
...restOpts,
|
|
116
116
|
origin: url.origin,
|
|
117
117
|
path: url.search ? `${url.pathname}${url.search}` : url.pathname,
|
|
118
118
|
method: opts.method || (opts.body ? 'PUT' : 'GET')
|
package/lib/core/util.js
CHANGED
|
@@ -12,8 +12,6 @@ const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
|
|
12
12
|
const { headerNameLowerCasedRecord } = require('./constants')
|
|
13
13
|
const { tree } = require('./tree')
|
|
14
14
|
|
|
15
|
-
const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(v => Number(v))
|
|
16
|
-
|
|
17
15
|
class BodyAsyncIterable {
|
|
18
16
|
constructor (body) {
|
|
19
17
|
this[kBody] = body
|
|
@@ -323,7 +321,7 @@ function isIterable (obj) {
|
|
|
323
321
|
*/
|
|
324
322
|
function hasSafeIterator (obj) {
|
|
325
323
|
const prototype = Object.getPrototypeOf(obj)
|
|
326
|
-
const ownIterator = Object.
|
|
324
|
+
const ownIterator = Object.hasOwn(obj, Symbol.iterator)
|
|
327
325
|
return ownIterator || (prototype != null && prototype !== Object.prototype && typeof obj[Symbol.iterator] === 'function')
|
|
328
326
|
}
|
|
329
327
|
|
|
@@ -989,8 +987,6 @@ module.exports = {
|
|
|
989
987
|
normalizedMethodRecords,
|
|
990
988
|
isValidPort,
|
|
991
989
|
isHttpOrHttpsPrefixed,
|
|
992
|
-
nodeMajor,
|
|
993
|
-
nodeMinor,
|
|
994
990
|
safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
|
|
995
991
|
wrapRequestBody,
|
|
996
992
|
setupConnectTimeout,
|
|
@@ -138,6 +138,10 @@ class DispatcherBase extends Dispatcher {
|
|
|
138
138
|
throw new InvalidArgumentError('opts must be an object.')
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
if (opts.dispatcher) {
|
|
142
|
+
throw new InvalidArgumentError('opts.dispatcher is not supported by instance methods. Pass opts.dispatcher to the top-level undici functions or call the dispatcher instance method directly.')
|
|
143
|
+
}
|
|
144
|
+
|
|
141
145
|
if (this[kDestroyed] || this[kOnDestroyed]) {
|
|
142
146
|
throw new ClientDestroyedError()
|
|
143
147
|
}
|
|
@@ -86,6 +86,12 @@ class Dispatcher1Wrapper extends Dispatcher {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
dispatch (opts, handler) {
|
|
89
|
+
// Legacy (v1) consumers do not support HTTP/2, so force HTTP/1.1.
|
|
90
|
+
// See https://github.com/nodejs/undici/issues/4989
|
|
91
|
+
if (opts.allowH2 !== false) {
|
|
92
|
+
opts = { ...opts, allowH2: false }
|
|
93
|
+
}
|
|
94
|
+
|
|
89
95
|
return this.#dispatcher.dispatch(opts, Dispatcher1Wrapper.wrapHandler(handler))
|
|
90
96
|
}
|
|
91
97
|
|
|
@@ -15,7 +15,7 @@ class H2CClient extends Client {
|
|
|
15
15
|
)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const {
|
|
18
|
+
const { maxConcurrentStreams, pipelining, ...opts } =
|
|
19
19
|
clientOpts ?? {}
|
|
20
20
|
let defaultMaxConcurrentStreams = 100
|
|
21
21
|
let defaultPipelining = 100
|
package/lib/dispatcher/pool.js
CHANGED
|
@@ -68,9 +68,6 @@ class Pool extends PoolBase {
|
|
|
68
68
|
this[kConnections] = connections || null
|
|
69
69
|
this[kUrl] = util.parseOrigin(origin)
|
|
70
70
|
this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl, socketPath }
|
|
71
|
-
this[kOptions].interceptors = options.interceptors
|
|
72
|
-
? { ...options.interceptors }
|
|
73
|
-
: undefined
|
|
74
71
|
this[kFactory] = factory
|
|
75
72
|
|
|
76
73
|
this.on('connect', (origin, targets) => {
|
|
@@ -104,7 +104,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
104
104
|
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
const { proxyTunnel = true } = opts
|
|
107
|
+
const { proxyTunnel = true, connectTimeout } = opts
|
|
108
108
|
|
|
109
109
|
super()
|
|
110
110
|
|
|
@@ -128,9 +128,9 @@ class ProxyAgent extends DispatcherBase {
|
|
|
128
128
|
this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}`
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
const connect = buildConnector({ ...opts.proxyTls })
|
|
132
|
-
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
|
|
133
|
-
this[kConnectEndpointHTTP1] = buildConnector({ ...opts.requestTls, allowH2: false })
|
|
131
|
+
const connect = buildConnector({ timeout: connectTimeout, ...opts.proxyTls })
|
|
132
|
+
this[kConnectEndpoint] = buildConnector({ timeout: connectTimeout, ...opts.requestTls })
|
|
133
|
+
this[kConnectEndpointHTTP1] = buildConnector({ timeout: connectTimeout, ...opts.requestTls, allowH2: false })
|
|
134
134
|
|
|
135
135
|
const agentFactory = opts.factory || defaultAgentFactory
|
|
136
136
|
const factory = (origin, options) => {
|
|
@@ -69,9 +69,6 @@ class RoundRobinPool extends PoolBase {
|
|
|
69
69
|
this[kConnections] = connections || null
|
|
70
70
|
this[kUrl] = util.parseOrigin(origin)
|
|
71
71
|
this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl, socketPath }
|
|
72
|
-
this[kOptions].interceptors = options.interceptors
|
|
73
|
-
? { ...options.interceptors }
|
|
74
|
-
: undefined
|
|
75
72
|
this[kFactory] = factory
|
|
76
73
|
this[kIndex] = -1
|
|
77
74
|
|
|
@@ -79,26 +79,28 @@ class Socks5ProxyAgent extends DispatcherBase {
|
|
|
79
79
|
debug('creating SOCKS5 connection to', proxyHost, proxyPort)
|
|
80
80
|
|
|
81
81
|
// Connect to the SOCKS5 proxy
|
|
82
|
-
const
|
|
83
|
-
const onConnect = () => {
|
|
84
|
-
socket.removeListener('error', onError)
|
|
85
|
-
resolve(socket)
|
|
86
|
-
}
|
|
82
|
+
const socketReady = Promise.withResolvers()
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
const onSocketConnect = () => {
|
|
85
|
+
socket.removeListener('error', onSocketError)
|
|
86
|
+
socketReady.resolve(socket)
|
|
87
|
+
}
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
const onSocketError = (err) => {
|
|
90
|
+
socket.removeListener('connect', onSocketConnect)
|
|
91
|
+
socketReady.reject(err)
|
|
92
|
+
}
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
const socket = net.connect({
|
|
95
|
+
host: proxyHost,
|
|
96
|
+
port: proxyPort
|
|
100
97
|
})
|
|
101
98
|
|
|
99
|
+
socket.once('connect', onSocketConnect)
|
|
100
|
+
socket.once('error', onSocketError)
|
|
101
|
+
|
|
102
|
+
await socketReady.promise
|
|
103
|
+
|
|
102
104
|
// Create SOCKS5 client
|
|
103
105
|
const socks5Client = new Socks5Client(socket, this[kProxyAuth])
|
|
104
106
|
|
|
@@ -112,58 +114,62 @@ class Socks5ProxyAgent extends DispatcherBase {
|
|
|
112
114
|
await socks5Client.handshake()
|
|
113
115
|
|
|
114
116
|
// Wait for authentication (if required)
|
|
115
|
-
|
|
116
|
-
const timeout = setTimeout(() => {
|
|
117
|
-
reject(new Error('SOCKS5 authentication timeout'))
|
|
118
|
-
}, 5000)
|
|
119
|
-
|
|
120
|
-
const onAuthenticated = () => {
|
|
121
|
-
clearTimeout(timeout)
|
|
122
|
-
socks5Client.removeListener('error', onError)
|
|
123
|
-
resolve()
|
|
124
|
-
}
|
|
117
|
+
const authenticationReady = Promise.withResolvers()
|
|
125
118
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
reject(err)
|
|
130
|
-
}
|
|
119
|
+
const authenticationTimeout = setTimeout(() => {
|
|
120
|
+
authenticationReady.reject(new Error('SOCKS5 authentication timeout'))
|
|
121
|
+
}, 5000)
|
|
131
122
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
const onAuthenticated = () => {
|
|
124
|
+
clearTimeout(authenticationTimeout)
|
|
125
|
+
socks5Client.removeListener('error', onAuthenticationError)
|
|
126
|
+
authenticationReady.resolve()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const onAuthenticationError = (err) => {
|
|
130
|
+
clearTimeout(authenticationTimeout)
|
|
131
|
+
socks5Client.removeListener('authenticated', onAuthenticated)
|
|
132
|
+
authenticationReady.reject(err)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if already authenticated (for NO_AUTH method)
|
|
136
|
+
if (socks5Client.state === 'authenticated') {
|
|
137
|
+
clearTimeout(authenticationTimeout)
|
|
138
|
+
authenticationReady.resolve()
|
|
139
|
+
} else {
|
|
140
|
+
socks5Client.once('authenticated', onAuthenticated)
|
|
141
|
+
socks5Client.once('error', onAuthenticationError)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await authenticationReady.promise
|
|
141
145
|
|
|
142
146
|
// Send CONNECT command
|
|
143
147
|
await socks5Client.connect(targetHost, targetPort)
|
|
144
148
|
|
|
145
149
|
// Wait for connection
|
|
146
|
-
|
|
147
|
-
const timeout = setTimeout(() => {
|
|
148
|
-
reject(new Error('SOCKS5 connection timeout'))
|
|
149
|
-
}, 5000)
|
|
150
|
-
|
|
151
|
-
const onConnected = (info) => {
|
|
152
|
-
debug('SOCKS5 tunnel established to', targetHost, targetPort, 'via', info)
|
|
153
|
-
clearTimeout(timeout)
|
|
154
|
-
socks5Client.removeListener('error', onError)
|
|
155
|
-
resolve()
|
|
156
|
-
}
|
|
150
|
+
const connectionReady = Promise.withResolvers()
|
|
157
151
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
reject(err)
|
|
162
|
-
}
|
|
152
|
+
const connectionTimeout = setTimeout(() => {
|
|
153
|
+
connectionReady.reject(new Error('SOCKS5 connection timeout'))
|
|
154
|
+
}, 5000)
|
|
163
155
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
const onConnected = (info) => {
|
|
157
|
+
debug('SOCKS5 tunnel established to', targetHost, targetPort, 'via', info)
|
|
158
|
+
clearTimeout(connectionTimeout)
|
|
159
|
+
socks5Client.removeListener('error', onConnectionError)
|
|
160
|
+
connectionReady.resolve()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const onConnectionError = (err) => {
|
|
164
|
+
clearTimeout(connectionTimeout)
|
|
165
|
+
socks5Client.removeListener('connected', onConnected)
|
|
166
|
+
connectionReady.reject(err)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
socks5Client.once('connected', onConnected)
|
|
170
|
+
socks5Client.once('error', onConnectionError)
|
|
171
|
+
|
|
172
|
+
await connectionReady.promise
|
|
167
173
|
|
|
168
174
|
return socket
|
|
169
175
|
}
|
|
@@ -206,10 +212,10 @@ class Socks5ProxyAgent extends DispatcherBase {
|
|
|
206
212
|
...connectOpts.tls || {}
|
|
207
213
|
})
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
const tlsReady = Promise.withResolvers()
|
|
216
|
+
finalSocket.once('secureConnect', tlsReady.resolve)
|
|
217
|
+
finalSocket.once('error', tlsReady.reject)
|
|
218
|
+
await tlsReady.promise
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
callback(null, finalSocket)
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
|
-
const { kBodyUsed } = require('../core/symbols')
|
|
5
4
|
const assert = require('node:assert')
|
|
6
5
|
const { InvalidArgumentError } = require('../core/errors')
|
|
7
|
-
const EE = require('node:events')
|
|
8
6
|
|
|
9
7
|
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
10
8
|
|
|
11
|
-
const kBody = Symbol('body')
|
|
12
|
-
|
|
13
9
|
const noop = () => {}
|
|
14
10
|
|
|
15
|
-
class BodyAsyncIterable {
|
|
16
|
-
constructor (body) {
|
|
17
|
-
this[kBody] = body
|
|
18
|
-
this[kBodyUsed] = false
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async * [Symbol.asyncIterator] () {
|
|
22
|
-
assert(!this[kBodyUsed], 'disturbed')
|
|
23
|
-
this[kBodyUsed] = true
|
|
24
|
-
yield * this[kBody]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
11
|
class RedirectHandler {
|
|
29
12
|
static buildDispatch (dispatcher, maxRedirections) {
|
|
30
13
|
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
|
|
@@ -44,43 +27,10 @@ class RedirectHandler {
|
|
|
44
27
|
this.location = null
|
|
45
28
|
const { maxRedirections: _, ...cleanOpts } = opts
|
|
46
29
|
this.opts = cleanOpts // opts must be a copy, exclude maxRedirections
|
|
30
|
+
this.opts.body = util.wrapRequestBody(this.opts.body)
|
|
47
31
|
this.maxRedirections = maxRedirections
|
|
48
32
|
this.handler = handler
|
|
49
33
|
this.history = []
|
|
50
|
-
|
|
51
|
-
if (util.isStream(this.opts.body)) {
|
|
52
|
-
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
|
|
53
|
-
// so that it can be dispatched again?
|
|
54
|
-
// TODO (fix): Do we need 100-expect support to provide a way to do this properly?
|
|
55
|
-
if (util.bodyLength(this.opts.body) === 0) {
|
|
56
|
-
this.opts.body
|
|
57
|
-
.on('data', function () {
|
|
58
|
-
assert(false)
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (typeof this.opts.body.readableDidRead !== 'boolean') {
|
|
63
|
-
this.opts.body[kBodyUsed] = false
|
|
64
|
-
EE.prototype.on.call(this.opts.body, 'data', function () {
|
|
65
|
-
this[kBodyUsed] = true
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
} else if (this.opts.body && typeof this.opts.body.pipeTo === 'function') {
|
|
69
|
-
// TODO (fix): We can't access ReadableStream internal state
|
|
70
|
-
// to determine whether or not it has been disturbed. This is just
|
|
71
|
-
// a workaround.
|
|
72
|
-
this.opts.body = new BodyAsyncIterable(this.opts.body)
|
|
73
|
-
} else if (
|
|
74
|
-
this.opts.body &&
|
|
75
|
-
typeof this.opts.body !== 'string' &&
|
|
76
|
-
!ArrayBuffer.isView(this.opts.body) &&
|
|
77
|
-
util.isIterable(this.opts.body) &&
|
|
78
|
-
!util.isFormDataLike(this.opts.body)
|
|
79
|
-
) {
|
|
80
|
-
// TODO: Should we allow re-using iterable if !this.opts.idempotent
|
|
81
|
-
// or through some other flag?
|
|
82
|
-
this.opts.body = new BodyAsyncIterable(this.opts.body)
|
|
83
|
-
}
|
|
84
34
|
}
|
|
85
35
|
|
|
86
36
|
onRequestStart (controller, context) {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = require('node:zlib')
|
|
4
4
|
const { pipeline } = require('node:stream')
|
|
5
5
|
const DecoratorHandler = require('../handler/decorator-handler')
|
|
6
|
-
const { runtimeFeatures } = require('../util/runtime-features')
|
|
7
6
|
|
|
8
7
|
/** @typedef {import('node:stream').Transform} Transform */
|
|
9
8
|
/** @typedef {import('node:stream').Transform} Controller */
|
|
@@ -17,7 +16,7 @@ const supportedEncodings = {
|
|
|
17
16
|
deflate: createInflate,
|
|
18
17
|
compress: createInflate,
|
|
19
18
|
'x-compress': createInflate,
|
|
20
|
-
|
|
19
|
+
zstd: createZstdDecompress
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
const defaultSkipStatusCodes = /** @type {const} */ ([204, 304])
|
package/lib/interceptor/dns.js
CHANGED
|
@@ -7,7 +7,7 @@ const maxInt = Math.pow(2, 31) - 1
|
|
|
7
7
|
|
|
8
8
|
function hasSafeIterator (headers) {
|
|
9
9
|
const prototype = Object.getPrototypeOf(headers)
|
|
10
|
-
const ownIterator = Object.
|
|
10
|
+
const ownIterator = Object.hasOwn(headers, Symbol.iterator)
|
|
11
11
|
return ownIterator || (prototype != null && prototype !== Object.prototype && typeof headers[Symbol.iterator] === 'function')
|
|
12
12
|
}
|
|
13
13
|
|
package/lib/util/cache.js
CHANGED
|
@@ -374,9 +374,11 @@ function assertCacheMethods (methods, name = 'CacheMethods') {
|
|
|
374
374
|
* @returns {string}
|
|
375
375
|
*/
|
|
376
376
|
function makeDeduplicationKey (cacheKey, excludeHeaders) {
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
377
|
+
// Use JSON.stringify to produce a collision-resistant key.
|
|
378
|
+
// Previous format used `:` and `=` delimiters without escaping, which
|
|
379
|
+
// allowed different header sets to produce identical keys (e.g.
|
|
380
|
+
// {a:"x:b=y"} vs {a:"x", b:"y"}). See: https://github.com/nodejs/undici/issues/5012
|
|
381
|
+
const headers = {}
|
|
380
382
|
|
|
381
383
|
if (cacheKey.headers) {
|
|
382
384
|
const sortedHeaders = Object.keys(cacheKey.headers).sort()
|
|
@@ -385,12 +387,11 @@ function makeDeduplicationKey (cacheKey, excludeHeaders) {
|
|
|
385
387
|
if (excludeHeaders?.has(header.toLowerCase())) {
|
|
386
388
|
continue
|
|
387
389
|
}
|
|
388
|
-
|
|
389
|
-
key += `:${header}=${Array.isArray(value) ? value.join(',') : value}`
|
|
390
|
+
headers[header] = cacheKey.headers[header]
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
|
|
393
|
-
return
|
|
394
|
+
return JSON.stringify([cacheKey.origin, cacheKey.method, cacheKey.path, headers])
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
module.exports = {
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
const lazyLoaders = {
|
|
7
7
|
__proto__: null,
|
|
8
8
|
'node:crypto': () => require('node:crypto'),
|
|
9
|
-
'node:sqlite': () => require('node:sqlite')
|
|
10
|
-
'node:worker_threads': () => require('node:worker_threads'),
|
|
11
|
-
'node:zlib': () => require('node:zlib')
|
|
9
|
+
'node:sqlite': () => require('node:sqlite')
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
/**
|
|
@@ -27,35 +25,9 @@ function detectRuntimeFeatureByNodeModule (moduleName) {
|
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* @param {NodeModuleName} moduleName
|
|
32
|
-
* @param {string} property
|
|
33
|
-
* @returns {boolean}
|
|
34
|
-
*/
|
|
35
|
-
function detectRuntimeFeatureByExportedProperty (moduleName, property) {
|
|
36
|
-
const module = lazyLoaders[moduleName]()
|
|
37
|
-
return typeof module[property] !== 'undefined'
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const runtimeFeaturesByExportedProperty = /** @type {const} */ (['markAsUncloneable', 'zstd'])
|
|
41
|
-
|
|
42
|
-
/** @type {Record<RuntimeFeatureByExportedProperty, [NodeModuleName, string]>} */
|
|
43
|
-
const exportedPropertyLookup = {
|
|
44
|
-
markAsUncloneable: ['node:worker_threads', 'markAsUncloneable'],
|
|
45
|
-
zstd: ['node:zlib', 'createZstdDecompress']
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** @typedef {typeof runtimeFeaturesByExportedProperty[number]} RuntimeFeatureByExportedProperty */
|
|
49
|
-
|
|
50
28
|
const runtimeFeaturesAsNodeModule = /** @type {const} */ (['crypto', 'sqlite'])
|
|
51
29
|
/** @typedef {typeof runtimeFeaturesAsNodeModule[number]} RuntimeFeatureByNodeModule */
|
|
52
|
-
|
|
53
|
-
const features = /** @type {const} */ ([
|
|
54
|
-
...runtimeFeaturesAsNodeModule,
|
|
55
|
-
...runtimeFeaturesByExportedProperty
|
|
56
|
-
])
|
|
57
|
-
|
|
58
|
-
/** @typedef {typeof features[number]} Feature */
|
|
30
|
+
/** @typedef {RuntimeFeatureByNodeModule} Feature */
|
|
59
31
|
|
|
60
32
|
/**
|
|
61
33
|
* @param {Feature} feature
|
|
@@ -64,9 +36,6 @@ const features = /** @type {const} */ ([
|
|
|
64
36
|
function detectRuntimeFeature (feature) {
|
|
65
37
|
if (runtimeFeaturesAsNodeModule.includes(/** @type {RuntimeFeatureByNodeModule} */ (feature))) {
|
|
66
38
|
return detectRuntimeFeatureByNodeModule(`node:${feature}`)
|
|
67
|
-
} else if (runtimeFeaturesByExportedProperty.includes(/** @type {RuntimeFeatureByExportedProperty} */ (feature))) {
|
|
68
|
-
const [moduleName, property] = exportedPropertyLookup[feature]
|
|
69
|
-
return detectRuntimeFeatureByExportedProperty(moduleName, property)
|
|
70
39
|
}
|
|
71
40
|
throw new TypeError(`unknown feature: ${feature}`)
|
|
72
41
|
}
|
|
@@ -101,7 +70,7 @@ class RuntimeFeatures {
|
|
|
101
70
|
* @param {boolean} value
|
|
102
71
|
*/
|
|
103
72
|
set (feature, value) {
|
|
104
|
-
if (
|
|
73
|
+
if (runtimeFeaturesAsNodeModule.includes(feature) === false) {
|
|
105
74
|
throw new TypeError(`unknown feature: ${feature}`)
|
|
106
75
|
}
|
|
107
76
|
this.#map.set(feature, value)
|
package/lib/web/cache/cache.js
CHANGED
|
@@ -10,8 +10,6 @@ const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch
|
|
|
10
10
|
const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
|
|
11
11
|
const { fetching } = require('../fetch/index')
|
|
12
12
|
const { urlIsHttpHttpsScheme, readAllBytes } = require('../fetch/util')
|
|
13
|
-
const { createDeferredPromise } = require('../../util/promise')
|
|
14
|
-
|
|
15
13
|
/**
|
|
16
14
|
* @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation
|
|
17
15
|
* @typedef {Object} CacheBatchOperation
|
|
@@ -153,7 +151,7 @@ class Cache {
|
|
|
153
151
|
requestList.push(r)
|
|
154
152
|
|
|
155
153
|
// 5.6
|
|
156
|
-
const responsePromise =
|
|
154
|
+
const responsePromise = Promise.withResolvers()
|
|
157
155
|
|
|
158
156
|
// 5.7
|
|
159
157
|
fetchControllers.push(fetching({
|
|
@@ -231,7 +229,7 @@ class Cache {
|
|
|
231
229
|
}
|
|
232
230
|
|
|
233
231
|
// 7.5
|
|
234
|
-
const cacheJobPromise =
|
|
232
|
+
const cacheJobPromise = Promise.withResolvers()
|
|
235
233
|
|
|
236
234
|
// 7.6.1
|
|
237
235
|
let errorData = null
|
|
@@ -325,7 +323,7 @@ class Cache {
|
|
|
325
323
|
const clonedResponse = cloneResponse(innerResponse)
|
|
326
324
|
|
|
327
325
|
// 10.
|
|
328
|
-
const bodyReadPromise =
|
|
326
|
+
const bodyReadPromise = Promise.withResolvers()
|
|
329
327
|
|
|
330
328
|
// 11.
|
|
331
329
|
if (innerResponse.body != null) {
|
|
@@ -364,7 +362,7 @@ class Cache {
|
|
|
364
362
|
}
|
|
365
363
|
|
|
366
364
|
// 19.1
|
|
367
|
-
const cacheJobPromise =
|
|
365
|
+
const cacheJobPromise = Promise.withResolvers()
|
|
368
366
|
|
|
369
367
|
// 19.2.1
|
|
370
368
|
let errorData = null
|
|
@@ -427,7 +425,7 @@ class Cache {
|
|
|
427
425
|
|
|
428
426
|
operations.push(operation)
|
|
429
427
|
|
|
430
|
-
const cacheJobPromise =
|
|
428
|
+
const cacheJobPromise = Promise.withResolvers()
|
|
431
429
|
|
|
432
430
|
let errorData = null
|
|
433
431
|
let requestResponses
|
|
@@ -483,7 +481,7 @@ class Cache {
|
|
|
483
481
|
}
|
|
484
482
|
|
|
485
483
|
// 4.
|
|
486
|
-
const promise =
|
|
484
|
+
const promise = Promise.withResolvers()
|
|
487
485
|
|
|
488
486
|
// 5.
|
|
489
487
|
// 5.1
|
package/lib/web/fetch/body.js
CHANGED
|
@@ -14,7 +14,6 @@ const { isErrored, isDisturbed } = require('node:stream')
|
|
|
14
14
|
const { isUint8Array } = require('node:util/types')
|
|
15
15
|
const { serializeAMimeType } = require('./data-url')
|
|
16
16
|
const { multipartFormDataParser } = require('./formdata-parser')
|
|
17
|
-
const { createDeferredPromise } = require('../../util/promise')
|
|
18
17
|
const { parseJSONFromBytes } = require('../infra')
|
|
19
18
|
const { utf8DecodeBytes } = require('../../encoding')
|
|
20
19
|
const { runtimeFeatures } = require('../../util/runtime-features.js')
|
|
@@ -431,7 +430,7 @@ function consumeBody (object, convertBytesToJSValue, instance, getInternalState)
|
|
|
431
430
|
}
|
|
432
431
|
|
|
433
432
|
// 2. Let promise be a new promise.
|
|
434
|
-
const promise =
|
|
433
|
+
const promise = Promise.withResolvers()
|
|
435
434
|
|
|
436
435
|
// 3. Let errorSteps given error be to reject promise with error.
|
|
437
436
|
const errorSteps = promise.reject
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -64,12 +64,7 @@ const { getGlobalDispatcher } = require('../../global')
|
|
|
64
64
|
const { webidl } = require('../webidl')
|
|
65
65
|
const { STATUS_CODES } = require('node:http')
|
|
66
66
|
const { bytesMatch } = require('../subresource-integrity/subresource-integrity')
|
|
67
|
-
const { createDeferredPromise } = require('../../util/promise')
|
|
68
67
|
const { isomorphicEncode } = require('../infra')
|
|
69
|
-
const { runtimeFeatures } = require('../../util/runtime-features')
|
|
70
|
-
|
|
71
|
-
// Node.js v23.8.0+ and v22.15.0+ supports Zstandard
|
|
72
|
-
const hasZstd = runtimeFeatures.has('zstd')
|
|
73
68
|
|
|
74
69
|
const GET_OR_HEAD = ['GET', 'HEAD']
|
|
75
70
|
|
|
@@ -136,7 +131,7 @@ function fetch (input, init = undefined) {
|
|
|
136
131
|
webidl.argumentLengthCheck(arguments, 1, 'globalThis.fetch')
|
|
137
132
|
|
|
138
133
|
// 1. Let p be a new promise.
|
|
139
|
-
let p =
|
|
134
|
+
let p = Promise.withResolvers()
|
|
140
135
|
|
|
141
136
|
// 2. Let requestObject be the result of invoking the initial value of
|
|
142
137
|
// Request as constructor with input and init as arguments. If this throws
|
|
@@ -1644,12 +1639,25 @@ async function httpNetworkOrCacheFetch (
|
|
|
1644
1639
|
// 14. If response’s status is 401, httpRequest’s response tainting is not "cors",
|
|
1645
1640
|
// includeCredentials is true, and request’s traversable for user prompts is
|
|
1646
1641
|
// a traversable navigable:
|
|
1647
|
-
|
|
1642
|
+
//
|
|
1643
|
+
// In Node.js there is no traversable navigable to prompt the user, but we
|
|
1644
|
+
// still need to handle URL-embedded credentials so authentication retries
|
|
1645
|
+
// for WebSocket handshakes continue to work.
|
|
1646
|
+
if (response.status === 401 && httpRequest.responseTainting !== 'cors' && includeCredentials && (
|
|
1647
|
+
request.useURLCredentials !== undefined ||
|
|
1648
|
+
isTraversableNavigable(request.traversableForUserPrompts)
|
|
1649
|
+
)) {
|
|
1648
1650
|
// 2. If request’s body is non-null, then:
|
|
1649
1651
|
if (request.body != null) {
|
|
1650
1652
|
// 1. If request’s body’s source is null, then return a network error.
|
|
1651
1653
|
if (request.body.source == null) {
|
|
1652
|
-
|
|
1654
|
+
// Note: In Node.js, this code path should not be reached because
|
|
1655
|
+
// isTraversableNavigable() returns false for non-navigable contexts.
|
|
1656
|
+
// However, we handle it gracefully by returning the response instead of
|
|
1657
|
+
// a network error, as we won't actually retry the request.
|
|
1658
|
+
// This aligns with the Fetch spec discussion in whatwg/fetch#1132,
|
|
1659
|
+
// which allows implementations flexibility when credentials can't be obtained.
|
|
1660
|
+
return response
|
|
1653
1661
|
}
|
|
1654
1662
|
|
|
1655
1663
|
// 2. Set request’s body to the body of the result of safely extracting
|
|
@@ -2251,7 +2259,7 @@ async function httpNetworkFetch (
|
|
|
2251
2259
|
flush: zlib.constants.BROTLI_OPERATION_FLUSH,
|
|
2252
2260
|
finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
|
|
2253
2261
|
}))
|
|
2254
|
-
} else if (coding === 'zstd'
|
|
2262
|
+
} else if (coding === 'zstd') {
|
|
2255
2263
|
decoders.push(zlib.createZstdDecompress({
|
|
2256
2264
|
flush: zlib.constants.ZSTD_e_continue,
|
|
2257
2265
|
finishFlush: zlib.constants.ZSTD_e_end
|
package/lib/web/fetch/util.js
CHANGED
|
@@ -1447,8 +1447,10 @@ function includesCredentials (url) {
|
|
|
1447
1447
|
* @param {object|string} navigable
|
|
1448
1448
|
*/
|
|
1449
1449
|
function isTraversableNavigable (navigable) {
|
|
1450
|
-
//
|
|
1451
|
-
|
|
1450
|
+
// Returns true only if we have an actual traversable navigable object
|
|
1451
|
+
// that can prompt the user for credentials. In Node.js, this will always
|
|
1452
|
+
// be false since there's no Window object or navigable.
|
|
1453
|
+
return navigable != null && navigable !== 'client' && navigable !== 'no-traversable'
|
|
1452
1454
|
}
|
|
1453
1455
|
|
|
1454
1456
|
class EnvironmentSettingsObjectBase {
|
package/lib/web/webidl/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
const { types, inspect } = require('node:util')
|
|
5
|
-
const {
|
|
5
|
+
const { markAsUncloneable } = require('node:worker_threads')
|
|
6
6
|
|
|
7
7
|
const UNDEFINED = 1
|
|
8
8
|
const BOOLEAN = 2
|
|
@@ -158,9 +158,7 @@ webidl.util.TypeValueToString = function (o) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
webidl.util.markAsUncloneable =
|
|
162
|
-
? require('node:worker_threads').markAsUncloneable
|
|
163
|
-
: () => {}
|
|
161
|
+
webidl.util.markAsUncloneable = markAsUncloneable
|
|
164
162
|
|
|
165
163
|
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
|
166
164
|
webidl.util.ConvertToInt = function (V, bitLength, signedness, flags) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { createDeferredPromise } = require('../../../util/promise')
|
|
4
3
|
const { environmentSettingsObject } = require('../../fetch/util')
|
|
5
4
|
const { states, opcodes, sentCloseFrameState } = require('../constants')
|
|
6
5
|
const { webidl } = require('../../webidl')
|
|
@@ -21,11 +20,11 @@ class WebSocketStream {
|
|
|
21
20
|
#url
|
|
22
21
|
|
|
23
22
|
// Each WebSocketStream object has an associated opened promise , which is a promise.
|
|
24
|
-
/** @type {
|
|
23
|
+
/** @type {ReturnType<typeof Promise.withResolvers>} */
|
|
25
24
|
#openedPromise
|
|
26
25
|
|
|
27
26
|
// Each WebSocketStream object has an associated closed promise , which is a promise.
|
|
28
|
-
/** @type {
|
|
27
|
+
/** @type {ReturnType<typeof Promise.withResolvers>} */
|
|
29
28
|
#closedPromise
|
|
30
29
|
|
|
31
30
|
// Each WebSocketStream object has an associated readable stream , which is a ReadableStream .
|
|
@@ -113,8 +112,8 @@ class WebSocketStream {
|
|
|
113
112
|
this.#url = urlRecord.toString()
|
|
114
113
|
|
|
115
114
|
// 6. Set this 's opened promise and closed promise to new promises.
|
|
116
|
-
this.#openedPromise =
|
|
117
|
-
this.#closedPromise =
|
|
115
|
+
this.#openedPromise = Promise.withResolvers()
|
|
116
|
+
this.#closedPromise = Promise.withResolvers()
|
|
118
117
|
|
|
119
118
|
// 7. Apply backpressure to the WebSocket.
|
|
120
119
|
// TODO
|
|
@@ -202,7 +201,7 @@ class WebSocketStream {
|
|
|
202
201
|
chunk = webidl.converters.WebSocketStreamWrite(chunk)
|
|
203
202
|
|
|
204
203
|
// 1. Let promise be a new promise created in stream ’s relevant realm .
|
|
205
|
-
const promise =
|
|
204
|
+
const promise = Promise.withResolvers()
|
|
206
205
|
|
|
207
206
|
// 2. Let data be null.
|
|
208
207
|
let data = null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.3",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"c8": "^10.0.0",
|
|
120
120
|
"cross-env": "^10.0.0",
|
|
121
121
|
"dns-packet": "^5.4.0",
|
|
122
|
-
"esbuild": "^0.
|
|
122
|
+
"esbuild": "^0.28.0",
|
|
123
123
|
"eslint": "^9.9.0",
|
|
124
124
|
"fast-check": "^4.1.1",
|
|
125
125
|
"husky": "^9.0.7",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"jsondiffpatch": "^0.7.3",
|
|
128
128
|
"neostandard": "^0.12.0",
|
|
129
129
|
"node-forge": "^1.3.1",
|
|
130
|
-
"proxy": "^
|
|
130
|
+
"proxy": "^4.0.0",
|
|
131
131
|
"tsd": "^0.33.0",
|
|
132
132
|
"typescript": "^6.0.2",
|
|
133
133
|
"ws": "^8.11.0"
|
package/types/agent.d.ts
CHANGED
|
@@ -22,8 +22,6 @@ declare namespace Agent {
|
|
|
22
22
|
export interface Options extends Pool.Options {
|
|
23
23
|
/** Default: `(origin, opts) => new Pool(origin, opts)`. */
|
|
24
24
|
factory?(origin: string | URL, opts: Object): Dispatcher;
|
|
25
|
-
|
|
26
|
-
interceptors?: { Agent?: readonly Dispatcher.DispatchInterceptor[] } & Pool.Options['interceptors']
|
|
27
25
|
maxOrigins?: number
|
|
28
26
|
}
|
|
29
27
|
|
package/types/client.d.ts
CHANGED
|
@@ -30,12 +30,7 @@ export class Client extends Dispatcher {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export declare namespace Client {
|
|
33
|
-
export interface OptionsInterceptors {
|
|
34
|
-
Client: readonly Dispatcher.DispatchInterceptor[];
|
|
35
|
-
}
|
|
36
33
|
export interface Options {
|
|
37
|
-
/** TODO */
|
|
38
|
-
interceptors?: OptionsInterceptors;
|
|
39
34
|
/** The maximum length of request headers in bytes. Default: Node.js' `--max-http-header-size` or `16384` (16KiB). */
|
|
40
35
|
maxHeaderSize?: number;
|
|
41
36
|
/** The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default: `300e3` milliseconds (300s). */
|
|
@@ -44,7 +39,7 @@ export declare namespace Client {
|
|
|
44
39
|
socketTimeout?: never;
|
|
45
40
|
/** @deprecated unsupported requestTimeout, use headersTimeout & bodyTimeout instead */
|
|
46
41
|
requestTimeout?: never;
|
|
47
|
-
/**
|
|
42
|
+
/** The timeout for establishing a socket connection, in milliseconds. Use `0` to disable it entirely. Default: `10e3` milliseconds (10s). */
|
|
48
43
|
connectTimeout?: number;
|
|
49
44
|
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Default: `300e3` milliseconds (300s). */
|
|
50
45
|
bodyTimeout?: number;
|
|
@@ -60,7 +55,7 @@ export declare namespace Client {
|
|
|
60
55
|
keepAliveMaxTimeout?: number;
|
|
61
56
|
/** A number of milliseconds subtracted from server *keep-alive* hints when overriding `idleTimeout` to account for timing inaccuracies caused by e.g. transport latency. Default: `1e3` milliseconds (1s). */
|
|
62
57
|
keepAliveTimeoutThreshold?: number;
|
|
63
|
-
/**
|
|
58
|
+
/** An IPC endpoint, either a Unix domain socket or Windows named pipe. Default: `null`. */
|
|
64
59
|
socketPath?: string;
|
|
65
60
|
/** The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Default: `1`. */
|
|
66
61
|
pipelining?: number;
|
|
@@ -68,13 +63,13 @@ export declare namespace Client {
|
|
|
68
63
|
tls?: never;
|
|
69
64
|
/** If `true`, an error is thrown when the request content-length header doesn't match the length of the request body. Default: `true`. */
|
|
70
65
|
strictContentLength?: boolean;
|
|
71
|
-
/**
|
|
66
|
+
/** Maximum number of TLS cached sessions used by the built-in connector. Use `0` to disable TLS session caching. Default: `100`. */
|
|
72
67
|
maxCachedSessions?: number;
|
|
73
|
-
/**
|
|
68
|
+
/** Connector options passed to `buildConnector`, or a custom connector function. Default: `null`. */
|
|
74
69
|
connect?: Partial<buildConnector.BuildOptions> | buildConnector.connector;
|
|
75
|
-
/**
|
|
70
|
+
/** The maximum number of requests to send over a single connection before it is reset. Use `0` to disable this limit. Default: `null`. */
|
|
76
71
|
maxRequestsPerClient?: number;
|
|
77
|
-
/**
|
|
72
|
+
/** Local IP address the socket should connect from. */
|
|
78
73
|
localAddress?: string;
|
|
79
74
|
/** Max response body size in bytes, -1 is disabled */
|
|
80
75
|
maxResponseSize?: number;
|
|
@@ -84,7 +79,7 @@ export declare namespace Client {
|
|
|
84
79
|
autoSelectFamilyAttemptTimeout?: number;
|
|
85
80
|
/**
|
|
86
81
|
* @description Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
|
|
87
|
-
* @default
|
|
82
|
+
* @default true
|
|
88
83
|
*/
|
|
89
84
|
allowH2?: boolean;
|
|
90
85
|
/**
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -8,8 +8,6 @@ import { FormData } from './formdata'
|
|
|
8
8
|
import Errors from './errors'
|
|
9
9
|
import { Autocomplete } from './utility'
|
|
10
10
|
|
|
11
|
-
type AbortSignal = unknown
|
|
12
|
-
|
|
13
11
|
export default Dispatcher
|
|
14
12
|
|
|
15
13
|
export type UndiciHeaders = Record<string, string | string[]> | IncomingHttpHeaders | string[] | Iterable<[string, string | string[] | undefined]> | null
|
package/types/h2c-client.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare namespace H2CClient {
|
|
|
32
32
|
maxHeaderSize?: number;
|
|
33
33
|
/** The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default: `300e3` milliseconds (300s). */
|
|
34
34
|
headersTimeout?: number;
|
|
35
|
-
/**
|
|
35
|
+
/** The timeout for establishing a socket connection, in milliseconds. Use `0` to disable it entirely. Default: `10e3` milliseconds (10s). */
|
|
36
36
|
connectTimeout?: number;
|
|
37
37
|
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Default: `300e3` milliseconds (300s). */
|
|
38
38
|
bodyTimeout?: number;
|
|
@@ -42,19 +42,19 @@ export declare namespace H2CClient {
|
|
|
42
42
|
keepAliveMaxTimeout?: number;
|
|
43
43
|
/** A number of milliseconds subtracted from server *keep-alive* hints when overriding `idleTimeout` to account for timing inaccuracies caused by e.g. transport latency. Default: `1e3` milliseconds (1s). */
|
|
44
44
|
keepAliveTimeoutThreshold?: number;
|
|
45
|
-
/**
|
|
45
|
+
/** An IPC endpoint, either a Unix domain socket or Windows named pipe. Default: `null`. */
|
|
46
46
|
socketPath?: string;
|
|
47
47
|
/** The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Default: `1`. */
|
|
48
48
|
pipelining?: number;
|
|
49
49
|
/** If `true`, an error is thrown when the request content-length header doesn't match the length of the request body. Default: `true`. */
|
|
50
50
|
strictContentLength?: boolean;
|
|
51
|
-
/**
|
|
51
|
+
/** Maximum number of TLS cached sessions used by the built-in connector. Use `0` to disable TLS session caching. Default: `100`. */
|
|
52
52
|
maxCachedSessions?: number;
|
|
53
|
-
/**
|
|
53
|
+
/** Connector options passed to `buildConnector`, or a custom connector function. Default: `null`. */
|
|
54
54
|
connect?: Omit<Partial<buildConnector.BuildOptions>, 'allowH2'> | buildConnector.connector;
|
|
55
|
-
/**
|
|
55
|
+
/** The maximum number of requests to send over a single connection before it is reset. Use `0` to disable this limit. Default: `null`. */
|
|
56
56
|
maxRequestsPerClient?: number;
|
|
57
|
-
/**
|
|
57
|
+
/** Local IP address the socket should connect from. */
|
|
58
58
|
localAddress?: string;
|
|
59
59
|
/** Max response body size in bytes, -1 is disabled */
|
|
60
60
|
maxResponseSize?: number;
|
package/types/pool.d.ts
CHANGED
|
@@ -35,7 +35,5 @@ declare namespace Pool {
|
|
|
35
35
|
connections?: number | null;
|
|
36
36
|
/** The amount of time before a client is removed from the pool and closed. `null` if no time limit. Default `null` */
|
|
37
37
|
clientTtl?: number | null;
|
|
38
|
-
|
|
39
|
-
interceptors?: { Pool?: readonly Dispatcher.DispatchInterceptor[] } & Client.Options['interceptors']
|
|
40
38
|
}
|
|
41
39
|
}
|
|
@@ -35,7 +35,5 @@ declare namespace RoundRobinPool {
|
|
|
35
35
|
connections?: number | null;
|
|
36
36
|
/** The amount of time before a client is removed from the pool and closed. `null` if no time limit. Default `null` */
|
|
37
37
|
clientTtl?: number | null;
|
|
38
|
-
|
|
39
|
-
interceptors?: { RoundRobinPool?: readonly Dispatcher.DispatchInterceptor[] } & Client.Options['interceptors']
|
|
40
38
|
}
|
|
41
39
|
}
|
package/types/webidl.d.ts
CHANGED
package/lib/util/promise.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @template {*} T
|
|
5
|
-
* @typedef {Object} DeferredPromise
|
|
6
|
-
* @property {Promise<T>} promise
|
|
7
|
-
* @property {(value?: T) => void} resolve
|
|
8
|
-
* @property {(reason?: any) => void} reject
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @template {*} T
|
|
13
|
-
* @returns {DeferredPromise<T>} An object containing a promise and its resolve/reject methods.
|
|
14
|
-
*/
|
|
15
|
-
function createDeferredPromise () {
|
|
16
|
-
let res
|
|
17
|
-
let rej
|
|
18
|
-
const promise = new Promise((resolve, reject) => {
|
|
19
|
-
res = resolve
|
|
20
|
-
rej = reject
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
return { promise, resolve: res, reject: rej }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
module.exports = {
|
|
27
|
-
createDeferredPromise
|
|
28
|
-
}
|