undici 8.12.1 → 8.12.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/Client.md +7 -2
- package/docs/docs/api/Interceptors.md +15 -2
- package/lib/core/symbols.js +1 -0
- package/lib/dispatcher/agent.js +6 -2
- package/lib/dispatcher/client-h1.js +0 -2
- package/lib/dispatcher/client-h2.js +78 -7
- package/lib/dispatcher/pool-base.js +20 -3
- package/lib/handler/cache-handler.js +58 -0
- package/lib/handler/deduplication-handler.js +57 -13
- package/lib/handler/redirect-handler.js +15 -1
- package/lib/interceptor/decompress.js +15 -5
- package/lib/web/eventsource/eventsource-stream.js +66 -44
- package/lib/web/fetch/index.js +3 -3
- package/lib/web/subresource-integrity/subresource-integrity.js +5 -5
- package/lib/web/websocket/receiver.js +13 -0
- package/package.json +3 -3
- package/types/api.d.ts +1 -1
- package/types/client.d.ts +6 -2
- package/types/utility.d.ts +4 -4
package/docs/docs/api/Client.md
CHANGED
|
@@ -148,8 +148,13 @@ added: v1.0.0
|
|
|
148
148
|
payloads, and decompressed (`permessage-deflate`) messages. Set to `0` to
|
|
149
149
|
disable the limit. **Default:** `134217728`.
|
|
150
150
|
* `eventSource` {Object} (optional) EventSource-specific configuration.
|
|
151
|
-
* `maxEventSize` {number} The maximum
|
|
152
|
-
|
|
151
|
+
* `maxEventSize` {number} The maximum total bytes of data and metadata
|
|
152
|
+
field values retained for a pending EventSource event. Each unfinished
|
|
153
|
+
line is separately limited to this value plus seven syntax bytes, before
|
|
154
|
+
its line ending arrives. The number of `data:` lines per event and input
|
|
155
|
+
chunks buffered for an unfinished line are each limited to
|
|
156
|
+
`min(131072, max(1024, floor(maxEventSize / 128)))` to bound object overhead.
|
|
157
|
+
Set to `0` to disable these limits.
|
|
153
158
|
**Default:** `buffer.kStringMaxLength`.
|
|
154
159
|
* Returns: {Client}
|
|
155
160
|
|
|
@@ -341,8 +341,21 @@ backing store.
|
|
|
341
341
|
|
|
342
342
|
**Parameters**
|
|
343
343
|
|
|
344
|
-
* `opts` {
|
|
345
|
-
|
|
344
|
+
* `opts` {Object} (optional)
|
|
345
|
+
* `store` {CacheStore} The store that cached responses are kept in. See
|
|
346
|
+
[`CacheStore`](./CacheStore.md). **Default:** a new `MemoryCacheStore`.
|
|
347
|
+
* `methods` {string[]} The HTTP methods to cache. Only safe methods are
|
|
348
|
+
allowed (`GET`, `HEAD`, `OPTIONS`, `TRACE`) and at least one is required.
|
|
349
|
+
**Default:** `['GET']`.
|
|
350
|
+
* `cacheByDefault` {number} RFC 9111 allows caching responses that are not
|
|
351
|
+
explicitly marked as cacheable or not cacheable. Set this to cache such
|
|
352
|
+
responses. **Default:** `undefined`.
|
|
353
|
+
* `type` {string} Whether the cache is `'shared'` or `'private'`, which
|
|
354
|
+
decides how directives such as `s-maxage` and `private` are applied.
|
|
355
|
+
**Default:** `'shared'`.
|
|
356
|
+
* `origins` {(string|RegExp)[]} Only requests to these origins are cached.
|
|
357
|
+
Strings are compared case-insensitively. **Default:** `undefined` (all
|
|
358
|
+
origins are cached).
|
|
346
359
|
|
|
347
360
|
**Returns:** {Dispatcher.DispatcherComposeInterceptor}
|
|
348
361
|
|
package/lib/core/symbols.js
CHANGED
|
@@ -6,6 +6,7 @@ module.exports = {
|
|
|
6
6
|
kDispatch: Symbol('dispatch'),
|
|
7
7
|
kUrl: Symbol('url'),
|
|
8
8
|
kRequestOrigin: Symbol('request origin'),
|
|
9
|
+
kRedirectCacheControl: Symbol('redirect cache control'),
|
|
9
10
|
kOriginless: Symbol('originless'),
|
|
10
11
|
kWriting: Symbol('writing'),
|
|
11
12
|
kResuming: Symbol('resuming'),
|
package/lib/dispatcher/agent.js
CHANGED
|
@@ -22,7 +22,7 @@ function defaultFactory (origin, opts) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
class Agent extends DispatcherBase {
|
|
25
|
-
constructor ({ factory = defaultFactory, maxOrigins = Infinity, connect, ...options } = {}) {
|
|
25
|
+
constructor ({ factory = defaultFactory, maxOrigins = Infinity, connect, tls, ...options } = {}) {
|
|
26
26
|
if (typeof factory !== 'function') {
|
|
27
27
|
throw new InvalidArgumentError('factory must be a function.')
|
|
28
28
|
}
|
|
@@ -41,7 +41,11 @@ class Agent extends DispatcherBase {
|
|
|
41
41
|
connect = { ...connect }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
if (tls && typeof tls !== 'function') {
|
|
45
|
+
tls = { ...tls }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this[kOptions] = { ...util.deepClone(options), maxOrigins, connect, tls }
|
|
45
49
|
this[kFactory] = factory
|
|
46
50
|
this[kClients] = new Map()
|
|
47
51
|
this[kOrigins] = new Set()
|
|
@@ -988,8 +988,6 @@ function connectH1 (client, socket) {
|
|
|
988
988
|
}
|
|
989
989
|
|
|
990
990
|
function onHttpSocketError (err) {
|
|
991
|
-
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
992
|
-
|
|
993
991
|
const parser = this[kParser]
|
|
994
992
|
|
|
995
993
|
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
|
@@ -61,6 +61,12 @@ const kRefusedStreamRetry = Symbol('refused stream retry')
|
|
|
61
61
|
// event loop.
|
|
62
62
|
const MAX_GOAWAY_REPLAY_ATTEMPTS = 1
|
|
63
63
|
|
|
64
|
+
// Lower bound on how long a liveness PING may take to be acked. Stream
|
|
65
|
+
// timeouts can be tiny (tens of ms), while an ack on a live connection can take
|
|
66
|
+
// longer than that on a loaded event loop; without a floor a slow but healthy
|
|
67
|
+
// connection would be torn down.
|
|
68
|
+
const MIN_PROBE_TIMEOUT = 1e3
|
|
69
|
+
|
|
64
70
|
let extractBody
|
|
65
71
|
|
|
66
72
|
/** @type {import('http2')} */
|
|
@@ -277,6 +283,9 @@ function connectH2 (client, socket) {
|
|
|
277
283
|
// Armed while the peer advertises MAX_CONCURRENT_STREAMS = 0 and we have
|
|
278
284
|
// work that cannot start. See setNoStreamsTimeout.
|
|
279
285
|
noStreamsTimeout: null,
|
|
286
|
+
// Armed while a PING checks whether the peer is still there after a
|
|
287
|
+
// stream timed out. See probeHttp2Session.
|
|
288
|
+
probeTimeout: null,
|
|
280
289
|
// Sockets start out ref'd. Session ref/unref proxies to the socket, so a
|
|
281
290
|
// single cached flag lets us skip redundant uv ref/unref calls, provided
|
|
282
291
|
// every ref/unref of the session or its socket goes through
|
|
@@ -361,6 +370,11 @@ function connectH2 (client, socket) {
|
|
|
361
370
|
return true
|
|
362
371
|
}
|
|
363
372
|
|
|
373
|
+
// Do not open streams on a connection that may be dead.
|
|
374
|
+
if (session[kHTTP2SessionState].probeTimeout != null) {
|
|
375
|
+
return true
|
|
376
|
+
}
|
|
377
|
+
|
|
364
378
|
if (client[kRunning] >= client[kMaxConcurrentStreams]) {
|
|
365
379
|
return true
|
|
366
380
|
}
|
|
@@ -494,6 +508,60 @@ function onNoStreamsTimeout (session) {
|
|
|
494
508
|
resetHttp2Session(session, err)
|
|
495
509
|
}
|
|
496
510
|
|
|
511
|
+
// A stream timeout only resets that stream, unlike HTTP/1.1 which destroys the
|
|
512
|
+
// socket. If the connection itself silently died (dropped by a NAT or load
|
|
513
|
+
// balancer, a hung peer), every new stream would time out in turn and the
|
|
514
|
+
// session would never be replaced. Ask the peer for a PING ack within the
|
|
515
|
+
// timeout that just fired (but at least MIN_PROBE_TIMEOUT): if it answers, only
|
|
516
|
+
// that request was slow; if not, drop the session so queued requests move to a
|
|
517
|
+
// fresh connection.
|
|
518
|
+
function probeHttp2Session (session, timeout) {
|
|
519
|
+
const state = session[kHTTP2SessionState]
|
|
520
|
+
|
|
521
|
+
if (state.probeTimeout != null || session.closed || session.destroyed) {
|
|
522
|
+
return
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
session.ping(onHttp2ProbeAck.bind(session))
|
|
527
|
+
} catch {
|
|
528
|
+
// The session is going away on its own.
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
state.probeTimeout = setTimeout(onHttp2ProbeTimeout, Math.max(timeout, MIN_PROBE_TIMEOUT), session).unref()
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function clearHttp2ProbeTimeout (session) {
|
|
536
|
+
const state = session[kHTTP2SessionState]
|
|
537
|
+
|
|
538
|
+
if (state?.probeTimeout != null) {
|
|
539
|
+
clearTimeout(state.probeTimeout)
|
|
540
|
+
state.probeTimeout = null
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function onHttp2ProbeAck () {
|
|
545
|
+
if (this[kHTTP2SessionState].probeTimeout == null) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
clearHttp2ProbeTimeout(this)
|
|
550
|
+
this[kClient][kResume]()
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function onHttp2ProbeTimeout (session) {
|
|
554
|
+
session[kHTTP2SessionState].probeTimeout = null
|
|
555
|
+
|
|
556
|
+
if (session.closed || session.destroyed) {
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const err = new SocketError('HTTP/2: connection unresponsive', util.getSocketInfo(session[kSocket]))
|
|
561
|
+
session[kError] = err
|
|
562
|
+
resetHttp2Session(session, err)
|
|
563
|
+
}
|
|
564
|
+
|
|
497
565
|
function clearHttp2IdleTimeout (session) {
|
|
498
566
|
const state = session[kHTTP2SessionState]
|
|
499
567
|
|
|
@@ -600,8 +668,6 @@ function onHttp2SendPing (session) {
|
|
|
600
668
|
}
|
|
601
669
|
|
|
602
670
|
function onHttp2SessionError (err) {
|
|
603
|
-
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
604
|
-
|
|
605
671
|
this[kSocket][kError] = err
|
|
606
672
|
|
|
607
673
|
if (this[kReceivedGoAway]) {
|
|
@@ -688,6 +754,7 @@ function onHttp2SessionGoAway (errorCode, lastStreamID) {
|
|
|
688
754
|
|
|
689
755
|
clearHttp2IdleTimeout(this)
|
|
690
756
|
clearNoStreamsTimeout(this)
|
|
757
|
+
clearHttp2ProbeTimeout(this)
|
|
691
758
|
|
|
692
759
|
if (!this.closed && !this.destroyed) {
|
|
693
760
|
this.close()
|
|
@@ -713,6 +780,7 @@ function onHttp2SessionClose () {
|
|
|
713
780
|
|
|
714
781
|
clearHttp2IdleTimeout(this)
|
|
715
782
|
clearNoStreamsTimeout(this)
|
|
783
|
+
clearHttp2ProbeTimeout(this)
|
|
716
784
|
|
|
717
785
|
if (state.ping.interval != null) {
|
|
718
786
|
clearInterval(state.ping.interval)
|
|
@@ -767,8 +835,6 @@ function onHttp2SocketClose () {
|
|
|
767
835
|
}
|
|
768
836
|
|
|
769
837
|
function onHttp2SocketError (err) {
|
|
770
|
-
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
771
|
-
|
|
772
838
|
this[kError] = err
|
|
773
839
|
|
|
774
840
|
if (this[kHTTP2Session]?.[kReceivedGoAway]) {
|
|
@@ -1614,9 +1680,14 @@ function onTimeout () {
|
|
|
1614
1680
|
// Remove self so timeout doesn't fire again after we handle it
|
|
1615
1681
|
stream.off('timeout', onTimeout)
|
|
1616
1682
|
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1683
|
+
const { responseReceived, session, bodyTimeout, headersTimeout } = state
|
|
1684
|
+
const err = responseReceived
|
|
1685
|
+
? new BodyTimeoutError(`HTTP/2: "stream timeout after ${bodyTimeout}"`)
|
|
1686
|
+
: new HeadersTimeoutError(`HTTP/2: "headers timeout after ${headersTimeout}"`)
|
|
1687
|
+
|
|
1688
|
+
// Probe before aborting: completing the request resumes the queue, which
|
|
1689
|
+
// must not open new streams until the peer has proven it is alive.
|
|
1690
|
+
probeHttp2Session(session, responseReceived ? bodyTimeout : headersTimeout)
|
|
1620
1691
|
state.abort(err)
|
|
1621
1692
|
}
|
|
1622
1693
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { AsyncResource } = require('node:async_hooks')
|
|
3
4
|
const { PoolStats } = require('../util/stats.js')
|
|
4
5
|
const DispatcherBase = require('./dispatcher-base')
|
|
5
6
|
const FixedQueue = require('./fixed-queue')
|
|
@@ -26,6 +27,22 @@ const kRetiring = Symbol('retiring clients')
|
|
|
26
27
|
// Closes every live client, including clients that have been
|
|
27
28
|
// taken out of rotation but are still finishing their own
|
|
28
29
|
// requests.
|
|
30
|
+
// Requests that wait in the Pool queue are dispatched later from whichever
|
|
31
|
+
// callback drains the queue (another request's connect, drain, ...). Capture
|
|
32
|
+
// the caller's async context so Client#dispatch, and the diagnostics channel
|
|
33
|
+
// events it publishes, still run in it.
|
|
34
|
+
class QueuedRequest extends AsyncResource {
|
|
35
|
+
constructor (opts, handler) {
|
|
36
|
+
super('UNDICI_POOL_QUEUED_REQUEST')
|
|
37
|
+
this.opts = opts
|
|
38
|
+
this.handler = handler
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dispatch (dispatcher) {
|
|
42
|
+
return this.runInAsyncScope(dispatcher.dispatch, dispatcher, this.opts, this.handler)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
29
46
|
function closeClients (pool) {
|
|
30
47
|
const closeAll = []
|
|
31
48
|
for (let i = 0; i < pool[kClients].length; i++) {
|
|
@@ -69,7 +86,7 @@ class PoolBase extends DispatcherBase {
|
|
|
69
86
|
break
|
|
70
87
|
}
|
|
71
88
|
this[kQueued]--
|
|
72
|
-
needDrain = !
|
|
89
|
+
needDrain = !item.dispatch(client)
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
client[kNeedDrain] = needDrain
|
|
@@ -114,7 +131,7 @@ class PoolBase extends DispatcherBase {
|
|
|
114
131
|
const item = queue.shift()
|
|
115
132
|
this[kQueued]--
|
|
116
133
|
|
|
117
|
-
if (!
|
|
134
|
+
if (!item.dispatch(dispatcher)) {
|
|
118
135
|
dispatcher[kNeedDrain] = true
|
|
119
136
|
this[kOnClientBusy](dispatcher)
|
|
120
137
|
hasDispatcher = this[kHasDispatcher]()
|
|
@@ -228,7 +245,7 @@ class PoolBase extends DispatcherBase {
|
|
|
228
245
|
|
|
229
246
|
if (!dispatcher) {
|
|
230
247
|
this[kNeedDrain] = true
|
|
231
|
-
this[kQueue].push(
|
|
248
|
+
this[kQueue].push(new QueuedRequest(opts, handler))
|
|
232
249
|
this[kQueued]++
|
|
233
250
|
} else if (!dispatcher.dispatch(opts, handler)) {
|
|
234
251
|
dispatcher[kNeedDrain] = true
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
|
+
const { kRedirectCacheControl } = require('../core/symbols')
|
|
4
5
|
const {
|
|
5
6
|
parseCacheControlHeader,
|
|
6
7
|
hasInvalidCacheControlDirective,
|
|
@@ -57,6 +58,19 @@ class CacheController {
|
|
|
57
58
|
set rawTrailers (value) { this.target.rawTrailers = value }
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @param {string | URL} a
|
|
63
|
+
* @param {string | URL} b
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
function sameOrigin (a, b) {
|
|
67
|
+
try {
|
|
68
|
+
return new URL(String(a)).origin === new URL(String(b)).origin
|
|
69
|
+
} catch {
|
|
70
|
+
return String(a) === String(b)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
// Status codes that we can use some heuristics on to cache
|
|
61
75
|
const HEURISTICALLY_CACHEABLE_STATUS_CODES = [
|
|
62
76
|
200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501
|
|
@@ -203,6 +217,14 @@ class CacheHandler {
|
|
|
203
217
|
*/
|
|
204
218
|
#writeStream
|
|
205
219
|
|
|
220
|
+
/**
|
|
221
|
+
* The URLs visited while following redirects, shared by reference from the
|
|
222
|
+
* redirect handler through the request context. Carries the suppressed hops'
|
|
223
|
+
* cache-control under {@link kRedirectCacheControl}.
|
|
224
|
+
* @type {URL[] | undefined}
|
|
225
|
+
*/
|
|
226
|
+
#history
|
|
227
|
+
|
|
206
228
|
/**
|
|
207
229
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} opts
|
|
208
230
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
|
|
@@ -220,6 +242,13 @@ class CacheHandler {
|
|
|
220
242
|
this.#writeStream?.destroy()
|
|
221
243
|
this.#writeStream = undefined
|
|
222
244
|
this.#downstreamController = new CacheController(controller)
|
|
245
|
+
// The redirect handler threads the list of visited URLs through the
|
|
246
|
+
// handler-chain context. Keep a reference to it (it is mutated as redirects
|
|
247
|
+
// are followed) so onResponseStart can tell which request actually produced
|
|
248
|
+
// the response. Unlike the request options object, this reference is not
|
|
249
|
+
// broken by an interceptor that copies the options between the cache and
|
|
250
|
+
// the redirect.
|
|
251
|
+
this.#history = context?.history
|
|
223
252
|
this.#handler.onRequestStart?.(this.#downstreamController, context)
|
|
224
253
|
}
|
|
225
254
|
|
|
@@ -267,6 +296,35 @@ class CacheHandler {
|
|
|
267
296
|
return downstreamOnHeaders()
|
|
268
297
|
}
|
|
269
298
|
|
|
299
|
+
// A cross-origin redirect can change the origin that actually produced the
|
|
300
|
+
// final response. Keying that response on the original (trusted) origin
|
|
301
|
+
// would let a redirect target control an entry for a URL the application
|
|
302
|
+
// trusts. The final request is the last URL in the redirect history; only
|
|
303
|
+
// cache when its origin still matches the cache key origin.
|
|
304
|
+
if (this.#history && this.#history.length > 0) {
|
|
305
|
+
const finalUrl = this.#history[this.#history.length - 1]
|
|
306
|
+
if (!sameOrigin(finalUrl.origin, this.#cacheKey.origin)) {
|
|
307
|
+
return downstreamOnHeaders()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// A redirect hop that is not forwarded can still carry a restrictive
|
|
312
|
+
// storage policy (for example `no-store`, or `private` for a shared cache).
|
|
313
|
+
// Honor it, otherwise a redirect that must not be stored could be laundered
|
|
314
|
+
// into a cacheable final response under the original key.
|
|
315
|
+
const redirectCacheControl = this.#history?.[kRedirectCacheControl]
|
|
316
|
+
if (redirectCacheControl) {
|
|
317
|
+
for (const hopCacheControl of redirectCacheControl) {
|
|
318
|
+
const hopDirectives = hopCacheControl ? parseCacheControlHeader(hopCacheControl) : {}
|
|
319
|
+
if (
|
|
320
|
+
hopDirectives['no-store'] === true ||
|
|
321
|
+
(this.#cacheType === 'shared' && hopDirectives.private === true)
|
|
322
|
+
) {
|
|
323
|
+
return downstreamOnHeaders()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
270
328
|
const cacheControlHeader = resHeaders['cache-control']
|
|
271
329
|
const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
|
|
272
330
|
|
|
@@ -16,6 +16,7 @@ const DEFAULT_MAX_BUFFER_SIZE = 5 * 1024 * 1024
|
|
|
16
16
|
* @property {number} bufferedBytes
|
|
17
17
|
* @property {object | null} pendingTrailers
|
|
18
18
|
* @property {boolean} done
|
|
19
|
+
* @property {boolean} [pendingStart] onRequestStart deferred until the primary request's context is known
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -30,6 +31,20 @@ class DeduplicationHandler {
|
|
|
30
31
|
*/
|
|
31
32
|
#primaryHandler
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Whether the primary request has started (onRequestStart received). Until
|
|
36
|
+
* then the request context is unknown and cannot be given to waiting handlers.
|
|
37
|
+
* @type {boolean}
|
|
38
|
+
*/
|
|
39
|
+
#requestStarted = false
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The primary request context, replayed to waiting handlers so they observe
|
|
43
|
+
* the same redirect history as the primary.
|
|
44
|
+
* @type {any}
|
|
45
|
+
*/
|
|
46
|
+
#context = null
|
|
47
|
+
|
|
33
48
|
/**
|
|
34
49
|
* @type {WaitingHandler[]}
|
|
35
50
|
*/
|
|
@@ -112,20 +127,30 @@ class DeduplicationHandler {
|
|
|
112
127
|
const waitingController = waitingHandler.controller
|
|
113
128
|
|
|
114
129
|
try {
|
|
115
|
-
|
|
130
|
+
if (this.#requestStarted) {
|
|
131
|
+
// The primary request has started, so its context (including the
|
|
132
|
+
// redirect history) is known and can be delivered immediately.
|
|
133
|
+
handler.onRequestStart?.(waitingController, this.#context)
|
|
116
134
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
if (waitingController.aborted) {
|
|
136
|
+
waitingHandler.done = true
|
|
137
|
+
return true
|
|
138
|
+
}
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
140
|
+
if (this.#responseStarted) {
|
|
141
|
+
handler.onResponseStart?.(
|
|
142
|
+
waitingController,
|
|
143
|
+
this.#statusCode,
|
|
144
|
+
this.#headers,
|
|
145
|
+
this.#statusMessage
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// The primary request has not started yet. Defer onRequestStart until
|
|
150
|
+
// its context is known, so the waiting handler observes the same
|
|
151
|
+
// redirect history as the primary (a cache handler relies on it to
|
|
152
|
+
// reject cross-origin redirect responses).
|
|
153
|
+
waitingHandler.pendingStart = true
|
|
129
154
|
}
|
|
130
155
|
} catch {
|
|
131
156
|
// Ignore errors from waiting handlers
|
|
@@ -146,6 +171,24 @@ class DeduplicationHandler {
|
|
|
146
171
|
*/
|
|
147
172
|
onRequestStart (controller, context) {
|
|
148
173
|
this.#controller = controller
|
|
174
|
+
if (!this.#requestStarted) {
|
|
175
|
+
this.#requestStarted = true
|
|
176
|
+
this.#context = context
|
|
177
|
+
// Deliver the now-known context to handlers that joined before the
|
|
178
|
+
// primary request started.
|
|
179
|
+
for (const waitingHandler of this.#waitingHandlers) {
|
|
180
|
+
if (waitingHandler.pendingStart && !waitingHandler.done && !waitingHandler.controller.aborted) {
|
|
181
|
+
waitingHandler.pendingStart = false
|
|
182
|
+
try {
|
|
183
|
+
waitingHandler.handler.onRequestStart?.(waitingHandler.controller, context)
|
|
184
|
+
} catch {
|
|
185
|
+
// Ignore errors from waiting handlers
|
|
186
|
+
waitingHandler.done = true
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
this.#pruneDoneWaitingHandlers()
|
|
191
|
+
}
|
|
149
192
|
this.#primaryHandler.onRequestStart?.(controller, context)
|
|
150
193
|
}
|
|
151
194
|
|
|
@@ -330,7 +373,8 @@ class DeduplicationHandler {
|
|
|
330
373
|
bufferedChunks: [],
|
|
331
374
|
bufferedBytes: 0,
|
|
332
375
|
pendingTrailers: null,
|
|
333
|
-
done: false
|
|
376
|
+
done: false,
|
|
377
|
+
pendingStart: false
|
|
334
378
|
}
|
|
335
379
|
|
|
336
380
|
const state = {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const util = require('../core/util')
|
|
4
4
|
const assert = require('node:assert')
|
|
5
5
|
const { InvalidArgumentError, MaxRedirectsError } = require('../core/errors')
|
|
6
|
-
const { kRequestOrigin } = require('../core/symbols')
|
|
6
|
+
const { kRequestOrigin, kRedirectCacheControl } = require('../core/symbols')
|
|
7
7
|
|
|
8
8
|
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
9
9
|
|
|
@@ -38,6 +38,15 @@ class RedirectHandler {
|
|
|
38
38
|
this.maxRedirections = maxRedirections
|
|
39
39
|
this.handler = handler
|
|
40
40
|
this.history = []
|
|
41
|
+
// `cache-control` of each intermediate (suppressed) redirect hop. An outer
|
|
42
|
+
// cache uses it to preserve a hop's storage policy (e.g. `no-store`) that
|
|
43
|
+
// would otherwise be lost because the hop's response is not forwarded. It
|
|
44
|
+
// travels as a non-enumerable property of the history array so it reaches
|
|
45
|
+
// the cache without becoming part of the public request/response context.
|
|
46
|
+
Object.defineProperty(this.history, kRedirectCacheControl, {
|
|
47
|
+
value: [],
|
|
48
|
+
enumerable: false
|
|
49
|
+
})
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
onRequestStart (controller, context) {
|
|
@@ -114,6 +123,11 @@ class RedirectHandler {
|
|
|
114
123
|
return
|
|
115
124
|
}
|
|
116
125
|
|
|
126
|
+
// This hop's response is suppressed (only its Location is used). Record its
|
|
127
|
+
// cache-control so an outer cache can honor a restrictive policy that would
|
|
128
|
+
// otherwise be lost when the final response is stored.
|
|
129
|
+
this.history[kRedirectCacheControl].push(headers['cache-control'])
|
|
130
|
+
|
|
117
131
|
const baseUrl = requestOrigin
|
|
118
132
|
? new URL(this.opts.path, requestOrigin)
|
|
119
133
|
: undefined
|
|
@@ -223,9 +223,14 @@ class DecompressHandler extends DecoratorHandler {
|
|
|
223
223
|
this.#decompressedSize = decompressedSize
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.#controller.
|
|
226
|
+
try {
|
|
227
|
+
const result = super.onResponseData(this.#controller, chunk)
|
|
228
|
+
if (result === false && !this.#controller.paused) {
|
|
229
|
+
this.#controller.pause()
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
this.#fail(err)
|
|
233
|
+
return
|
|
229
234
|
}
|
|
230
235
|
}
|
|
231
236
|
} while (this.#drainRequested && !this.#terminated && !this.#controller.paused)
|
|
@@ -242,9 +247,14 @@ class DecompressHandler extends DecoratorHandler {
|
|
|
242
247
|
return
|
|
243
248
|
}
|
|
244
249
|
|
|
245
|
-
this.#terminated = true
|
|
246
250
|
this.#cleanupDecompressors()
|
|
247
|
-
|
|
251
|
+
try {
|
|
252
|
+
super.onResponseEnd(this.#controller, this.#trailers)
|
|
253
|
+
} catch (err) {
|
|
254
|
+
this.#fail(err)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
this.#terminated = true
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
#onDecompressionEnd () {
|
|
@@ -31,12 +31,11 @@ const EVENT = Buffer.from('event')
|
|
|
31
31
|
const ID = Buffer.from('id')
|
|
32
32
|
const RETRY = Buffer.from('retry')
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const DATA_FIELD_PREFIX_SIZE = DATA.length + 2
|
|
34
|
+
// Each data field creates a string node, and each unconsumed input chunk keeps
|
|
35
|
+
// a Buffer wrapper. The absolute ceiling protects the large default byte limit.
|
|
36
|
+
const minParserItems = 1024
|
|
37
|
+
const maxParserItems = 131072
|
|
38
|
+
const bytesPerParserItem = 128
|
|
40
39
|
|
|
41
40
|
function isASCIINumberBytes (buffer, start) {
|
|
42
41
|
if (start >= buffer.length) {
|
|
@@ -130,9 +129,13 @@ class EventSourceStream extends Transform {
|
|
|
130
129
|
pos = 0
|
|
131
130
|
lineChunkIndex = 0
|
|
132
131
|
linePos = 0
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
lineLength = 0
|
|
133
|
+
eventSize = 0
|
|
134
|
+
eventFieldSizes = { data: 0, event: 0, id: 0, retry: 0 }
|
|
135
|
+
dataLineCount = 0
|
|
135
136
|
maxEventSize
|
|
137
|
+
maxDataLines
|
|
138
|
+
maxBufferedChunks
|
|
136
139
|
|
|
137
140
|
event = {
|
|
138
141
|
data: undefined,
|
|
@@ -157,6 +160,11 @@ class EventSourceStream extends Transform {
|
|
|
157
160
|
|
|
158
161
|
this.state = options.eventSourceSettings || {}
|
|
159
162
|
this.maxEventSize = options.maxEventSize ?? defaultMaxEventSize
|
|
163
|
+
const parserItemLimit = this.maxEventSize > 0
|
|
164
|
+
? Math.min(maxParserItems, Math.max(minParserItems, Math.floor(this.maxEventSize / bytesPerParserItem)))
|
|
165
|
+
: 0
|
|
166
|
+
this.maxDataLines = parserItemLimit
|
|
167
|
+
this.maxBufferedChunks = parserItemLimit
|
|
160
168
|
if (options.push) {
|
|
161
169
|
this.push = options.push
|
|
162
170
|
}
|
|
@@ -174,6 +182,14 @@ class EventSourceStream extends Transform {
|
|
|
174
182
|
return
|
|
175
183
|
}
|
|
176
184
|
|
|
185
|
+
// A leading line ending can immediately release the currently buffered
|
|
186
|
+
// line, even when it uses the final allowed chunk slot.
|
|
187
|
+
if (this.maxEventSize > 0 &&
|
|
188
|
+
this.chunks.length >= this.maxBufferedChunks &&
|
|
189
|
+
chunk[0] !== LF && chunk[0] !== CR) {
|
|
190
|
+
callback(createMaxEventSizeExceededError())
|
|
191
|
+
return
|
|
192
|
+
}
|
|
177
193
|
this.chunks.push(chunk)
|
|
178
194
|
|
|
179
195
|
// Strip leading byte-order-mark if we opened the stream and started
|
|
@@ -266,21 +282,25 @@ class EventSourceStream extends Transform {
|
|
|
266
282
|
continue
|
|
267
283
|
}
|
|
268
284
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// maxEventSize here to prevent unbounded buffering when a server streams a
|
|
274
|
-
// field that never contains a line terminator.
|
|
275
|
-
if (this.currentLineSize > this.getMaxEventSizeLineLength()) {
|
|
285
|
+
// readLine() is only called after CR or LF. Reject an unfinished line
|
|
286
|
+
// before it can retain an unbounded number of decoded chunks. The seven
|
|
287
|
+
// extra bytes allow the longest field prefix ("event: " or "retry: ").
|
|
288
|
+
if (this.maxEventSize > 0 && ++this.lineLength > this.maxEventSize + 7) {
|
|
276
289
|
callback(createMaxEventSizeExceededError())
|
|
277
290
|
return
|
|
278
291
|
}
|
|
292
|
+
this.advanceCursor()
|
|
279
293
|
}
|
|
280
294
|
|
|
281
295
|
callback()
|
|
282
296
|
}
|
|
283
297
|
|
|
298
|
+
_destroy (error, callback) {
|
|
299
|
+
this.chunks.length = 0
|
|
300
|
+
this.clearEvent()
|
|
301
|
+
super._destroy(error, callback)
|
|
302
|
+
}
|
|
303
|
+
|
|
284
304
|
/**
|
|
285
305
|
* @param {Buffer} line
|
|
286
306
|
* @param {EventSourceStreamEvent} event
|
|
@@ -317,12 +337,12 @@ class EventSourceStream extends Transform {
|
|
|
317
337
|
}
|
|
318
338
|
|
|
319
339
|
if (isFieldName(line, fieldLength, DATA)) {
|
|
320
|
-
|
|
321
|
-
const eventDataSize = this.eventDataSize + (event.data === undefined ? 0 : 1) + valueBytes
|
|
322
|
-
|
|
323
|
-
if (this.maxEventSize > 0 && eventDataSize > this.maxEventSize) {
|
|
340
|
+
if (this.maxEventSize > 0 && this.dataLineCount >= this.maxDataLines) {
|
|
324
341
|
throw createMaxEventSizeExceededError()
|
|
325
342
|
}
|
|
343
|
+
const valueBytes = line.length - valueStart
|
|
344
|
+
const dataSize = this.eventFieldSizes.data + (event.data === undefined ? 0 : 1) + valueBytes
|
|
345
|
+
this.setEventFieldSize('data', dataSize)
|
|
326
346
|
|
|
327
347
|
const value = line.toString('utf8', valueStart)
|
|
328
348
|
|
|
@@ -331,12 +351,13 @@ class EventSourceStream extends Transform {
|
|
|
331
351
|
} else {
|
|
332
352
|
event.data += `\n${value}`
|
|
333
353
|
}
|
|
334
|
-
this.
|
|
354
|
+
this.dataLineCount++
|
|
335
355
|
return
|
|
336
356
|
}
|
|
337
357
|
|
|
338
358
|
if (isFieldName(line, fieldLength, RETRY)) {
|
|
339
359
|
if (isASCIINumberBytes(line, valueStart)) {
|
|
360
|
+
this.setEventFieldSize('retry', line.length - valueStart)
|
|
340
361
|
event.retry = line.toString('utf8', valueStart)
|
|
341
362
|
}
|
|
342
363
|
return
|
|
@@ -344,20 +365,29 @@ class EventSourceStream extends Transform {
|
|
|
344
365
|
|
|
345
366
|
if (isFieldName(line, fieldLength, ID)) {
|
|
346
367
|
if (isValidLastEventIdBytes(line, valueStart)) {
|
|
368
|
+
this.setEventFieldSize('id', line.length - valueStart)
|
|
347
369
|
event.id = line.toString('utf8', valueStart)
|
|
348
370
|
}
|
|
349
371
|
return
|
|
350
372
|
}
|
|
351
373
|
|
|
352
374
|
if (isFieldName(line, fieldLength, EVENT)) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
event.event = value
|
|
375
|
+
if (valueStart < line.length) {
|
|
376
|
+
this.setEventFieldSize('event', line.length - valueStart)
|
|
377
|
+
event.event = line.toString('utf8', valueStart)
|
|
357
378
|
}
|
|
358
379
|
}
|
|
359
380
|
}
|
|
360
381
|
|
|
382
|
+
setEventFieldSize (field, size) {
|
|
383
|
+
const eventSize = this.eventSize - this.eventFieldSizes[field] + size
|
|
384
|
+
if (this.maxEventSize > 0 && eventSize > this.maxEventSize) {
|
|
385
|
+
throw createMaxEventSizeExceededError()
|
|
386
|
+
}
|
|
387
|
+
this.eventSize = eventSize
|
|
388
|
+
this.eventFieldSizes[field] = size
|
|
389
|
+
}
|
|
390
|
+
|
|
361
391
|
/**
|
|
362
392
|
* @param {EventSourceStreamEvent} event
|
|
363
393
|
*/
|
|
@@ -388,7 +418,12 @@ class EventSourceStream extends Transform {
|
|
|
388
418
|
this.event.event = undefined
|
|
389
419
|
this.event.id = undefined
|
|
390
420
|
this.event.retry = undefined
|
|
391
|
-
this.
|
|
421
|
+
this.eventSize = 0
|
|
422
|
+
this.dataLineCount = 0
|
|
423
|
+
this.eventFieldSizes.data = 0
|
|
424
|
+
this.eventFieldSizes.event = 0
|
|
425
|
+
this.eventFieldSizes.id = 0
|
|
426
|
+
this.eventFieldSizes.retry = 0
|
|
392
427
|
}
|
|
393
428
|
|
|
394
429
|
hasPendingEvent () {
|
|
@@ -424,29 +459,16 @@ class EventSourceStream extends Transform {
|
|
|
424
459
|
syncLineStartToCursor () {
|
|
425
460
|
this.lineChunkIndex = this.chunkIndex
|
|
426
461
|
this.linePos = this.pos
|
|
427
|
-
this.
|
|
462
|
+
this.lineLength = 0
|
|
428
463
|
this.dropConsumedChunks()
|
|
429
464
|
}
|
|
430
465
|
|
|
431
|
-
/**
|
|
432
|
-
* @returns {number} The maximum length, in bytes, of the currently buffered
|
|
433
|
-
* (possibly unterminated) line. The extra {@link DATA_FIELD_PREFIX_SIZE}
|
|
434
|
-
* accounts for the longest `data:` field prefix, so a terminated data line
|
|
435
|
-
* carrying exactly {@link maxEventSize} value bytes remains accepted.
|
|
436
|
-
*/
|
|
437
|
-
getMaxEventSizeLineLength () {
|
|
438
|
-
if (this.maxEventSize <= 0) {
|
|
439
|
-
return Infinity
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return this.maxEventSize + DATA_FIELD_PREFIX_SIZE
|
|
443
|
-
}
|
|
444
|
-
|
|
445
466
|
dropConsumedChunks () {
|
|
446
|
-
|
|
447
|
-
this.
|
|
448
|
-
this.
|
|
449
|
-
this.
|
|
467
|
+
if (this.lineChunkIndex > 0) {
|
|
468
|
+
const consumed = this.lineChunkIndex
|
|
469
|
+
this.chunks.splice(0, consumed)
|
|
470
|
+
this.lineChunkIndex = 0
|
|
471
|
+
this.chunkIndex -= consumed
|
|
450
472
|
}
|
|
451
473
|
|
|
452
474
|
if (this.chunkIndex === this.chunks.length) {
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -2280,8 +2280,6 @@ async function httpNetworkFetch (
|
|
|
2280
2280
|
appendHeadersListFromResponseHeaders(headersList, headers, rawHeaders)
|
|
2281
2281
|
const location = headersList.get('location', true)
|
|
2282
2282
|
|
|
2283
|
-
this.body = new Readable({ read: () => controller.resume() })
|
|
2284
|
-
|
|
2285
2283
|
const willFollow = location && request.redirect === 'follow' &&
|
|
2286
2284
|
redirectStatusSet.has(status)
|
|
2287
2285
|
|
|
@@ -2299,7 +2297,7 @@ async function httpNetworkFetch (
|
|
|
2299
2297
|
// CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206).
|
|
2300
2298
|
const maxContentEncodings = 5
|
|
2301
2299
|
if (codings.length > maxContentEncodings) {
|
|
2302
|
-
|
|
2300
|
+
controller.abort(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`))
|
|
2303
2301
|
return
|
|
2304
2302
|
}
|
|
2305
2303
|
|
|
@@ -2341,6 +2339,8 @@ async function httpNetworkFetch (
|
|
|
2341
2339
|
}
|
|
2342
2340
|
}
|
|
2343
2341
|
|
|
2342
|
+
this.body = new Readable({ read: () => controller.resume() })
|
|
2343
|
+
|
|
2344
2344
|
const onError = (err) => this.onResponseError(controller, err)
|
|
2345
2345
|
|
|
2346
2346
|
resolve({
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
const { runtimeFeatures } = require('../../util/runtime-features.js')
|
|
5
|
+
const { asciiLowercase } = require('../infra/index.js')
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {object} Metadata
|
|
@@ -66,14 +67,12 @@ const getSRIHashAlgorithmIndex = /** @type {GetSRIHashAlgorithmIndex} */ (Map.pr
|
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* @typedef IsValidSRIHashAlgorithm
|
|
70
|
+
* @see https://w3c.github.io/webappsec-subresource-integrity/#valid-sri-hash-algorithm-token
|
|
69
71
|
* @type {(algorithm: string) => algorithm is SRIHashAlgorithm}
|
|
70
72
|
* @param {*} algorithm
|
|
71
73
|
* @returns {algorithm is SRIHashAlgorithm}
|
|
72
74
|
*/
|
|
73
|
-
|
|
74
|
-
const isValidSRIHashAlgorithm = /** @type {IsValidSRIHashAlgorithm} */ (
|
|
75
|
-
Map.prototype.has.bind(validSRIHashAlgorithmTokenSet)
|
|
76
|
-
)
|
|
75
|
+
const isValidSRIHashAlgorithm = (algorithm) => validSRIHashAlgorithmTokenSet.has(asciiLowercase(algorithm))
|
|
77
76
|
|
|
78
77
|
/**
|
|
79
78
|
* @param {Uint8Array} bytes
|
|
@@ -229,7 +228,8 @@ function parseMetadata (metadata) {
|
|
|
229
228
|
// 8. Let metadata be the ordered map
|
|
230
229
|
// «["alg" → algorithm, "val" → base64-value]».
|
|
231
230
|
const metadata = {
|
|
232
|
-
|
|
231
|
+
// Possible spec bug?
|
|
232
|
+
alg: asciiLowercase(algorithm),
|
|
233
233
|
val: base64Value
|
|
234
234
|
}
|
|
235
235
|
|
|
@@ -252,6 +252,19 @@ class ByteParser extends Writable {
|
|
|
252
252
|
if (isControlFrame(this.#info.opcode)) {
|
|
253
253
|
this.#loop = this.parseControlFrame(body)
|
|
254
254
|
this.#state = parserStates.INFO
|
|
255
|
+
|
|
256
|
+
// If the automatic PONG/CLOSE reply filled the socket's write buffer,
|
|
257
|
+
// stop parsing and resume on drain so a PING flood cannot grow the
|
|
258
|
+
// outbound queue without bound.
|
|
259
|
+
if (this.#loop && this.#handler.socket.writableNeedDrain) {
|
|
260
|
+
this.#loop = false
|
|
261
|
+
this.#handler.socket.once('drain', () => {
|
|
262
|
+
if (this.destroyed) return
|
|
263
|
+
this.#loop = true
|
|
264
|
+
this.run(callback)
|
|
265
|
+
})
|
|
266
|
+
return
|
|
267
|
+
}
|
|
255
268
|
} else {
|
|
256
269
|
if (!this.#info.compressed) {
|
|
257
270
|
if (!this.writeFragments(body)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "8.12.
|
|
3
|
+
"version": "8.12.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": {
|
|
@@ -122,12 +122,12 @@
|
|
|
122
122
|
"cross-env": "^10.0.0",
|
|
123
123
|
"dns-packet": "^5.4.0",
|
|
124
124
|
"esbuild": "^0.28.0",
|
|
125
|
-
"eslint": "^
|
|
125
|
+
"eslint": "^10.12.0",
|
|
126
126
|
"fast-check": "^4.1.1",
|
|
127
127
|
"husky": "^9.0.7",
|
|
128
128
|
"jest": "^30.0.5",
|
|
129
129
|
"jsondiffpatch": "^0.7.3",
|
|
130
|
-
"neostandard": "^0.
|
|
130
|
+
"neostandard": "^0.14.0",
|
|
131
131
|
"node-forge": "^1.3.1",
|
|
132
132
|
"proxy": "^4.0.0",
|
|
133
133
|
"tsd": "^0.33.0",
|
package/types/api.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import Dispatcher from './dispatcher'
|
|
|
5
5
|
/** Performs an HTTP request. */
|
|
6
6
|
declare function request<TOpaque = null> (
|
|
7
7
|
url: string | URL | UrlObject,
|
|
8
|
-
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions<TOpaque>, 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions, 'method'
|
|
8
|
+
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions<TOpaque>, 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions, 'method'>>
|
|
9
9
|
): Promise<Dispatcher.ResponseData<TOpaque>>
|
|
10
10
|
|
|
11
11
|
/** A faster version of `request`. */
|
package/types/client.d.ts
CHANGED
|
@@ -175,8 +175,12 @@ export declare namespace Client {
|
|
|
175
175
|
}
|
|
176
176
|
export interface EventSourceOptions {
|
|
177
177
|
/**
|
|
178
|
-
* Maximum
|
|
179
|
-
*
|
|
178
|
+
* Maximum total bytes of data and metadata field values retained for a
|
|
179
|
+
* pending EventSource event. Each unfinished line is separately bounded
|
|
180
|
+
* to this value plus seven syntax bytes. The number of data lines per
|
|
181
|
+
* event and buffered input chunks are each limited to
|
|
182
|
+
* min(131072, max(1024, floor(maxEventSize / 128))).
|
|
183
|
+
* Set to 0 to disable these limits.
|
|
180
184
|
* @default buffer.kStringMaxLength
|
|
181
185
|
*/
|
|
182
186
|
maxEventSize?: number;
|
package/types/utility.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type AutocompletePrimitiveBaseType<T> =
|
|
2
|
-
T extends string ? string
|
|
3
|
-
T extends number ? number
|
|
4
|
-
T extends boolean ? boolean
|
|
5
|
-
never
|
|
2
|
+
T extends string ? string
|
|
3
|
+
: T extends number ? number
|
|
4
|
+
: T extends boolean ? boolean
|
|
5
|
+
: never
|
|
6
6
|
|
|
7
7
|
export type Autocomplete<T> = T | (AutocompletePrimitiveBaseType<T> & Record<never, never>)
|