undici 6.20.0 → 7.0.0-alpha.2

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 (137) hide show
  1. package/README.md +6 -10
  2. package/docs/docs/api/Agent.md +0 -3
  3. package/docs/docs/api/Client.md +1 -3
  4. package/docs/docs/api/Debug.md +1 -1
  5. package/docs/docs/api/Dispatcher.md +60 -8
  6. package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
  7. package/docs/docs/api/Fetch.md +1 -0
  8. package/docs/docs/api/MockAgent.md +2 -0
  9. package/docs/docs/api/MockPool.md +2 -1
  10. package/docs/docs/api/Pool.md +0 -1
  11. package/docs/docs/api/RetryAgent.md +1 -1
  12. package/docs/docs/api/RetryHandler.md +1 -1
  13. package/docs/docs/api/WebSocket.md +45 -3
  14. package/index.js +6 -6
  15. package/lib/api/abort-signal.js +2 -0
  16. package/lib/api/api-connect.js +3 -1
  17. package/lib/api/api-pipeline.js +7 -6
  18. package/lib/api/api-request.js +32 -47
  19. package/lib/api/api-stream.js +39 -50
  20. package/lib/api/api-upgrade.js +5 -3
  21. package/lib/api/readable.js +261 -64
  22. package/lib/api/util.js +2 -0
  23. package/lib/core/constants.js +11 -9
  24. package/lib/core/diagnostics.js +122 -128
  25. package/lib/core/errors.js +4 -4
  26. package/lib/core/request.js +11 -9
  27. package/lib/core/symbols.js +2 -1
  28. package/lib/core/tree.js +9 -1
  29. package/lib/core/util.js +219 -48
  30. package/lib/dispatcher/agent.js +3 -17
  31. package/lib/dispatcher/balanced-pool.js +5 -8
  32. package/lib/dispatcher/client-h1.js +278 -54
  33. package/lib/dispatcher/client-h2.js +1 -1
  34. package/lib/dispatcher/client.js +23 -34
  35. package/lib/dispatcher/dispatcher-base.js +2 -34
  36. package/lib/dispatcher/dispatcher.js +3 -24
  37. package/lib/dispatcher/fixed-queue.js +91 -49
  38. package/lib/dispatcher/pool-stats.js +2 -0
  39. package/lib/dispatcher/pool.js +3 -6
  40. package/lib/dispatcher/proxy-agent.js +6 -7
  41. package/lib/handler/decorator-handler.js +24 -0
  42. package/lib/handler/redirect-handler.js +11 -2
  43. package/lib/handler/retry-handler.js +12 -3
  44. package/lib/interceptor/dns.js +346 -0
  45. package/lib/interceptor/dump.js +2 -2
  46. package/lib/interceptor/redirect.js +11 -14
  47. package/lib/interceptor/response-error.js +4 -1
  48. package/lib/llhttp/constants.d.ts +97 -0
  49. package/lib/llhttp/constants.js +412 -192
  50. package/lib/llhttp/constants.js.map +1 -0
  51. package/lib/llhttp/llhttp-wasm.js +11 -1
  52. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  53. package/lib/llhttp/utils.d.ts +2 -0
  54. package/lib/llhttp/utils.js +9 -9
  55. package/lib/llhttp/utils.js.map +1 -0
  56. package/lib/mock/mock-agent.js +5 -8
  57. package/lib/mock/mock-client.js +9 -4
  58. package/lib/mock/mock-errors.js +3 -1
  59. package/lib/mock/mock-interceptor.js +8 -6
  60. package/lib/mock/mock-pool.js +9 -4
  61. package/lib/mock/mock-symbols.js +3 -1
  62. package/lib/mock/mock-utils.js +29 -5
  63. package/lib/web/cache/cache.js +24 -21
  64. package/lib/web/cache/cachestorage.js +1 -1
  65. package/lib/web/cookies/index.js +17 -13
  66. package/lib/web/cookies/parse.js +2 -2
  67. package/lib/web/eventsource/eventsource-stream.js +9 -8
  68. package/lib/web/eventsource/eventsource.js +10 -6
  69. package/lib/web/fetch/body.js +42 -36
  70. package/lib/web/fetch/constants.js +35 -26
  71. package/lib/web/fetch/data-url.js +1 -1
  72. package/lib/web/fetch/formdata-parser.js +2 -2
  73. package/lib/web/fetch/formdata.js +65 -54
  74. package/lib/web/fetch/headers.js +117 -85
  75. package/lib/web/fetch/index.js +55 -62
  76. package/lib/web/fetch/request.js +135 -77
  77. package/lib/web/fetch/response.js +86 -56
  78. package/lib/web/fetch/util.js +90 -64
  79. package/lib/web/fetch/webidl.js +99 -64
  80. package/lib/web/websocket/connection.js +76 -147
  81. package/lib/web/websocket/constants.js +3 -4
  82. package/lib/web/websocket/events.js +4 -2
  83. package/lib/web/websocket/frame.js +45 -3
  84. package/lib/web/websocket/receiver.js +29 -33
  85. package/lib/web/websocket/sender.js +18 -13
  86. package/lib/web/websocket/stream/websocketerror.js +83 -0
  87. package/lib/web/websocket/stream/websocketstream.js +485 -0
  88. package/lib/web/websocket/util.js +128 -77
  89. package/lib/web/websocket/websocket.js +234 -135
  90. package/package.json +20 -33
  91. package/scripts/strip-comments.js +3 -1
  92. package/types/agent.d.ts +7 -7
  93. package/types/api.d.ts +24 -24
  94. package/types/balanced-pool.d.ts +11 -11
  95. package/types/client.d.ts +11 -12
  96. package/types/diagnostics-channel.d.ts +10 -10
  97. package/types/dispatcher.d.ts +96 -97
  98. package/types/env-http-proxy-agent.d.ts +2 -2
  99. package/types/errors.d.ts +53 -47
  100. package/types/fetch.d.ts +8 -8
  101. package/types/formdata.d.ts +7 -7
  102. package/types/global-dispatcher.d.ts +4 -4
  103. package/types/global-origin.d.ts +5 -5
  104. package/types/handlers.d.ts +4 -4
  105. package/types/header.d.ts +157 -1
  106. package/types/index.d.ts +42 -46
  107. package/types/interceptors.d.ts +22 -8
  108. package/types/mock-agent.d.ts +21 -18
  109. package/types/mock-client.d.ts +4 -4
  110. package/types/mock-errors.d.ts +3 -3
  111. package/types/mock-interceptor.d.ts +19 -19
  112. package/types/mock-pool.d.ts +4 -4
  113. package/types/patch.d.ts +0 -4
  114. package/types/pool-stats.d.ts +8 -8
  115. package/types/pool.d.ts +12 -12
  116. package/types/proxy-agent.d.ts +4 -4
  117. package/types/readable.d.ts +22 -14
  118. package/types/retry-agent.d.ts +1 -1
  119. package/types/retry-handler.d.ts +8 -8
  120. package/types/util.d.ts +3 -3
  121. package/types/utility.d.ts +7 -0
  122. package/types/webidl.d.ts +44 -6
  123. package/types/websocket.d.ts +34 -1
  124. package/docs/docs/api/DispatchInterceptor.md +0 -60
  125. package/lib/interceptor/redirect-interceptor.js +0 -21
  126. package/lib/mock/pluralizer.js +0 -29
  127. package/lib/web/cache/symbols.js +0 -5
  128. package/lib/web/fetch/file.js +0 -126
  129. package/lib/web/fetch/symbols.js +0 -9
  130. package/lib/web/fileapi/encoding.js +0 -290
  131. package/lib/web/fileapi/filereader.js +0 -344
  132. package/lib/web/fileapi/progressevent.js +0 -78
  133. package/lib/web/fileapi/symbols.js +0 -10
  134. package/lib/web/fileapi/util.js +0 -391
  135. package/lib/web/websocket/symbols.js +0 -12
  136. package/types/file.d.ts +0 -39
  137. package/types/filereader.d.ts +0 -54
@@ -1,5 +1,3 @@
1
- // @ts-check
2
-
3
1
  'use strict'
4
2
 
5
3
  const assert = require('node:assert')
@@ -43,13 +41,11 @@ const {
43
41
  kBodyTimeout,
44
42
  kStrictContentLength,
45
43
  kConnector,
46
- kMaxRedirections,
47
44
  kMaxRequests,
48
45
  kCounter,
49
46
  kClose,
50
47
  kDestroy,
51
48
  kDispatch,
52
- kInterceptors,
53
49
  kLocalAddress,
54
50
  kMaxResponseSize,
55
51
  kOnError,
@@ -59,10 +55,18 @@ const {
59
55
  } = require('../core/symbols.js')
60
56
  const connectH1 = require('./client-h1.js')
61
57
  const connectH2 = require('./client-h2.js')
62
- let deprecatedInterceptorWarned = false
63
58
 
64
59
  const kClosedResolve = Symbol('kClosedResolve')
65
60
 
61
+ const getDefaultNodeMaxHeaderSize = http &&
62
+ http.maxHeaderSize &&
63
+ Number.isInteger(http.maxHeaderSize) &&
64
+ http.maxHeaderSize > 0
65
+ ? () => http.maxHeaderSize
66
+ : () => { throw new InvalidArgumentError('http module not available or http.maxHeaderSize invalid') }
67
+
68
+ const noop = () => {}
69
+
66
70
  function getPipelining (client) {
67
71
  return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1
68
72
  }
@@ -77,7 +81,6 @@ class Client extends DispatcherBase {
77
81
  * @param {import('../../types/client.js').Client.Options} options
78
82
  */
79
83
  constructor (url, {
80
- interceptors,
81
84
  maxHeaderSize,
82
85
  headersTimeout,
83
86
  socketTimeout,
@@ -95,7 +98,6 @@ class Client extends DispatcherBase {
95
98
  tls,
96
99
  strictContentLength,
97
100
  maxCachedSessions,
98
- maxRedirections,
99
101
  connect,
100
102
  maxRequestsPerClient,
101
103
  localAddress,
@@ -106,8 +108,6 @@ class Client extends DispatcherBase {
106
108
  maxConcurrentStreams,
107
109
  allowH2
108
110
  } = {}) {
109
- super()
110
-
111
111
  if (keepAlive !== undefined) {
112
112
  throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
113
113
  }
@@ -128,8 +128,14 @@ class Client extends DispatcherBase {
128
128
  throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead')
129
129
  }
130
130
 
131
- if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) {
132
- throw new InvalidArgumentError('invalid maxHeaderSize')
131
+ if (maxHeaderSize != null) {
132
+ if (!Number.isInteger(maxHeaderSize) || maxHeaderSize < 1) {
133
+ throw new InvalidArgumentError('invalid maxHeaderSize')
134
+ }
135
+ } else {
136
+ // If maxHeaderSize is not provided, use the default value from the http module
137
+ // or if that is not available, throw an error.
138
+ maxHeaderSize = getDefaultNodeMaxHeaderSize()
133
139
  }
134
140
 
135
141
  if (socketPath != null && typeof socketPath !== 'string') {
@@ -164,10 +170,6 @@ class Client extends DispatcherBase {
164
170
  throw new InvalidArgumentError('connect must be a function or an object')
165
171
  }
166
172
 
167
- if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
168
- throw new InvalidArgumentError('maxRedirections must be a positive number')
169
- }
170
-
171
173
  if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
172
174
  throw new InvalidArgumentError('maxRequestsPerClient must be a positive number')
173
175
  }
@@ -196,6 +198,8 @@ class Client extends DispatcherBase {
196
198
  throw new InvalidArgumentError('maxConcurrentStreams must be a positive integer, greater than 0')
197
199
  }
198
200
 
201
+ super()
202
+
199
203
  if (typeof connect !== 'function') {
200
204
  connect = buildConnector({
201
205
  ...tls,
@@ -208,22 +212,10 @@ class Client extends DispatcherBase {
208
212
  })
209
213
  }
210
214
 
211
- if (interceptors?.Client && Array.isArray(interceptors.Client)) {
212
- this[kInterceptors] = interceptors.Client
213
- if (!deprecatedInterceptorWarned) {
214
- deprecatedInterceptorWarned = true
215
- process.emitWarning('Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.', {
216
- code: 'UNDICI-CLIENT-INTERCEPTOR-DEPRECATED'
217
- })
218
- }
219
- } else {
220
- this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })]
221
- }
222
-
223
215
  this[kUrl] = util.parseOrigin(url)
224
216
  this[kConnector] = connect
225
217
  this[kPipelining] = pipelining != null ? pipelining : 1
226
- this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize
218
+ this[kMaxHeadersSize] = maxHeaderSize
227
219
  this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout
228
220
  this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout
229
221
  this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 2e3 : keepAliveTimeoutThreshold
@@ -236,7 +228,6 @@ class Client extends DispatcherBase {
236
228
  this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3
237
229
  this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3
238
230
  this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
239
- this[kMaxRedirections] = maxRedirections
240
231
  this[kMaxRequests] = maxRequestsPerClient
241
232
  this[kClosedResolve] = null
242
233
  this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1
@@ -362,8 +353,6 @@ class Client extends DispatcherBase {
362
353
  }
363
354
  }
364
355
 
365
- const createRedirectInterceptor = require('../interceptor/redirect-interceptor.js')
366
-
367
356
  function onError (client, err) {
368
357
  if (
369
358
  client[kRunning] === 0 &&
@@ -402,7 +391,7 @@ async function connect (client) {
402
391
  assert(idx !== -1)
403
392
  const ip = hostname.substring(1, idx)
404
393
 
405
- assert(net.isIP(ip))
394
+ assert(net.isIPv6(ip))
406
395
  hostname = ip
407
396
  }
408
397
 
@@ -442,7 +431,7 @@ async function connect (client) {
442
431
  })
443
432
 
444
433
  if (client.destroyed) {
445
- util.destroy(socket.on('error', () => {}), new ClientDestroyedError())
434
+ util.destroy(socket.on('error', noop), new ClientDestroyedError())
446
435
  return
447
436
  }
448
437
 
@@ -453,7 +442,7 @@ async function connect (client) {
453
442
  ? await connectH2(client, socket)
454
443
  : await connectH1(client, socket)
455
444
  } catch (err) {
456
- socket.destroy().on('error', () => {})
445
+ socket.destroy().on('error', noop)
457
446
  throw err
458
447
  }
459
448
 
@@ -6,11 +6,10 @@ const {
6
6
  ClientClosedError,
7
7
  InvalidArgumentError
8
8
  } = require('../core/errors')
9
- const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = require('../core/symbols')
9
+ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch } = require('../core/symbols')
10
10
 
11
11
  const kOnDestroyed = Symbol('onDestroyed')
12
12
  const kOnClosed = Symbol('onClosed')
13
- const kInterceptedDispatch = Symbol('Intercepted Dispatch')
14
13
 
15
14
  class DispatcherBase extends Dispatcher {
16
15
  constructor () {
@@ -30,23 +29,6 @@ class DispatcherBase extends Dispatcher {
30
29
  return this[kClosed]
31
30
  }
32
31
 
33
- get interceptors () {
34
- return this[kInterceptors]
35
- }
36
-
37
- set interceptors (newInterceptors) {
38
- if (newInterceptors) {
39
- for (let i = newInterceptors.length - 1; i >= 0; i--) {
40
- const interceptor = this[kInterceptors][i]
41
- if (typeof interceptor !== 'function') {
42
- throw new InvalidArgumentError('interceptor must be an function')
43
- }
44
- }
45
- }
46
-
47
- this[kInterceptors] = newInterceptors
48
- }
49
-
50
32
  close (callback) {
51
33
  if (callback === undefined) {
52
34
  return new Promise((resolve, reject) => {
@@ -142,20 +124,6 @@ class DispatcherBase extends Dispatcher {
142
124
  })
143
125
  }
144
126
 
145
- [kInterceptedDispatch] (opts, handler) {
146
- if (!this[kInterceptors] || this[kInterceptors].length === 0) {
147
- this[kInterceptedDispatch] = this[kDispatch]
148
- return this[kDispatch](opts, handler)
149
- }
150
-
151
- let dispatch = this[kDispatch].bind(this)
152
- for (let i = this[kInterceptors].length - 1; i >= 0; i--) {
153
- dispatch = this[kInterceptors][i](dispatch)
154
- }
155
- this[kInterceptedDispatch] = dispatch
156
- return dispatch(opts, handler)
157
- }
158
-
159
127
  dispatch (opts, handler) {
160
128
  if (!handler || typeof handler !== 'object') {
161
129
  throw new InvalidArgumentError('handler must be an object')
@@ -174,7 +142,7 @@ class DispatcherBase extends Dispatcher {
174
142
  throw new ClientClosedError()
175
143
  }
176
144
 
177
- return this[kInterceptedDispatch](opts, handler)
145
+ return this[kDispatch](opts, handler)
178
146
  } catch (err) {
179
147
  if (typeof handler.onError !== 'function') {
180
148
  throw new InvalidArgumentError('invalid onError method')
@@ -35,30 +35,9 @@ class Dispatcher extends EventEmitter {
35
35
  }
36
36
  }
37
37
 
38
- return new ComposedDispatcher(this, dispatch)
39
- }
40
- }
41
-
42
- class ComposedDispatcher extends Dispatcher {
43
- #dispatcher = null
44
- #dispatch = null
45
-
46
- constructor (dispatcher, dispatch) {
47
- super()
48
- this.#dispatcher = dispatcher
49
- this.#dispatch = dispatch
50
- }
51
-
52
- dispatch (...args) {
53
- this.#dispatch(...args)
54
- }
55
-
56
- close (...args) {
57
- return this.#dispatcher.close(...args)
58
- }
59
-
60
- destroy (...args) {
61
- return this.#dispatcher.destroy(...args)
38
+ return new Proxy(this, {
39
+ get: (target, key) => key === 'dispatch' ? dispatch : target[key]
40
+ })
62
41
  }
63
42
  }
64
43
 
@@ -1,12 +1,10 @@
1
- /* eslint-disable */
2
-
3
1
  'use strict'
4
2
 
5
3
  // Extracted from node/lib/internal/fixed_queue.js
6
4
 
7
5
  // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
8
- const kSize = 2048;
9
- const kMask = kSize - 1;
6
+ const kSize = 2048
7
+ const kMask = kSize - 1
10
8
 
11
9
  // The FixedQueue is implemented as a singly-linked list of fixed-size
12
10
  // circular buffers. It looks something like this:
@@ -17,18 +15,18 @@ const kMask = kSize - 1;
17
15
  // +-----------+ <-----\ +-----------+ <------\ +-----------+
18
16
  // | [null] | \----- | next | \------- | next |
19
17
  // +-----------+ +-----------+ +-----------+
20
- // | item | <-- bottom | item | <-- bottom | [empty] |
21
- // | item | | item | | [empty] |
22
- // | item | | item | | [empty] |
23
- // | item | | item | | [empty] |
18
+ // | item | <-- bottom | item | <-- bottom | undefined |
19
+ // | item | | item | | undefined |
20
+ // | item | | item | | undefined |
21
+ // | item | | item | | undefined |
24
22
  // | item | | item | bottom --> | item |
25
23
  // | item | | item | | item |
26
24
  // | ... | | ... | | ... |
27
25
  // | item | | item | | item |
28
26
  // | item | | item | | item |
29
- // | [empty] | <-- top | item | | item |
30
- // | [empty] | | item | | item |
31
- // | [empty] | | [empty] | <-- top top --> | [empty] |
27
+ // | undefined | <-- top | item | | item |
28
+ // | undefined | | item | | item |
29
+ // | undefined | | undefined | <-- top top --> | undefined |
32
30
  // +-----------+ +-----------+ +-----------+
33
31
  //
34
32
  // Or, if there is only one circular buffer, it looks something
@@ -40,12 +38,12 @@ const kMask = kSize - 1;
40
38
  // +-----------+ +-----------+
41
39
  // | [null] | | [null] |
42
40
  // +-----------+ +-----------+
43
- // | [empty] | | item |
44
- // | [empty] | | item |
45
- // | item | <-- bottom top --> | [empty] |
46
- // | item | | [empty] |
47
- // | [empty] | <-- top bottom --> | item |
48
- // | [empty] | | item |
41
+ // | undefined | | item |
42
+ // | undefined | | item |
43
+ // | item | <-- bottom top --> | undefined |
44
+ // | item | | undefined |
45
+ // | undefined | <-- top bottom --> | item |
46
+ // | undefined | | item |
49
47
  // +-----------+ +-----------+
50
48
  //
51
49
  // Adding a value means moving `top` forward by one, removing means
@@ -56,62 +54,106 @@ const kMask = kSize - 1;
56
54
  // `top + 1 === bottom` it's full. This wastes a single space of storage
57
55
  // but allows much quicker checks.
58
56
 
57
+ /**
58
+ * @type {FixedCircularBuffer}
59
+ * @template T
60
+ */
59
61
  class FixedCircularBuffer {
60
- constructor() {
61
- this.bottom = 0;
62
- this.top = 0;
63
- this.list = new Array(kSize);
64
- this.next = null;
62
+ constructor () {
63
+ /**
64
+ * @type {number}
65
+ */
66
+ this.bottom = 0
67
+ /**
68
+ * @type {number}
69
+ */
70
+ this.top = 0
71
+ /**
72
+ * @type {Array<T|undefined>}
73
+ */
74
+ this.list = new Array(kSize).fill(undefined)
75
+ /**
76
+ * @type {T|null}
77
+ */
78
+ this.next = null
65
79
  }
66
80
 
67
- isEmpty() {
68
- return this.top === this.bottom;
81
+ /**
82
+ * @returns {boolean}
83
+ */
84
+ isEmpty () {
85
+ return this.top === this.bottom
69
86
  }
70
87
 
71
- isFull() {
72
- return ((this.top + 1) & kMask) === this.bottom;
88
+ /**
89
+ * @returns {boolean}
90
+ */
91
+ isFull () {
92
+ return ((this.top + 1) & kMask) === this.bottom
73
93
  }
74
94
 
75
- push(data) {
76
- this.list[this.top] = data;
77
- this.top = (this.top + 1) & kMask;
95
+ /**
96
+ * @param {T} data
97
+ * @returns {void}
98
+ */
99
+ push (data) {
100
+ this.list[this.top] = data
101
+ this.top = (this.top + 1) & kMask
78
102
  }
79
103
 
80
- shift() {
81
- const nextItem = this.list[this.bottom];
82
- if (nextItem === undefined)
83
- return null;
84
- this.list[this.bottom] = undefined;
85
- this.bottom = (this.bottom + 1) & kMask;
86
- return nextItem;
104
+ /**
105
+ * @returns {T|null}
106
+ */
107
+ shift () {
108
+ const nextItem = this.list[this.bottom]
109
+ if (nextItem === undefined) { return null }
110
+ this.list[this.bottom] = undefined
111
+ this.bottom = (this.bottom + 1) & kMask
112
+ return nextItem
87
113
  }
88
114
  }
89
115
 
116
+ /**
117
+ * @template T
118
+ */
90
119
  module.exports = class FixedQueue {
91
- constructor() {
92
- this.head = this.tail = new FixedCircularBuffer();
120
+ constructor () {
121
+ /**
122
+ * @type {FixedCircularBuffer<T>}
123
+ */
124
+ this.head = this.tail = new FixedCircularBuffer()
93
125
  }
94
126
 
95
- isEmpty() {
96
- return this.head.isEmpty();
127
+ /**
128
+ * @returns {boolean}
129
+ */
130
+ isEmpty () {
131
+ return this.head.isEmpty()
97
132
  }
98
133
 
99
- push(data) {
134
+ /**
135
+ * @param {T} data
136
+ */
137
+ push (data) {
100
138
  if (this.head.isFull()) {
101
139
  // Head is full: Creates a new queue, sets the old queue's `.next` to it,
102
140
  // and sets it as the new main queue.
103
- this.head = this.head.next = new FixedCircularBuffer();
141
+ this.head = this.head.next = new FixedCircularBuffer()
104
142
  }
105
- this.head.push(data);
143
+ this.head.push(data)
106
144
  }
107
145
 
108
- shift() {
109
- const tail = this.tail;
110
- const next = tail.shift();
146
+ /**
147
+ * @returns {T|null}
148
+ */
149
+ shift () {
150
+ const tail = this.tail
151
+ const next = tail.shift()
111
152
  if (tail.isEmpty() && tail.next !== null) {
112
153
  // If there is another queue, it forms the new tail.
113
- this.tail = tail.next;
154
+ this.tail = tail.next
155
+ tail.next = null
114
156
  }
115
- return next;
157
+ return next
116
158
  }
117
- };
159
+ }
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require('../core/symbols')
2
4
  const kPool = Symbol('pool')
3
5
 
@@ -12,7 +12,7 @@ const {
12
12
  InvalidArgumentError
13
13
  } = require('../core/errors')
14
14
  const util = require('../core/util')
15
- const { kUrl, kInterceptors } = require('../core/symbols')
15
+ const { kUrl } = require('../core/symbols')
16
16
  const buildConnector = require('../core/connect')
17
17
 
18
18
  const kOptions = Symbol('options')
@@ -37,8 +37,6 @@ class Pool extends PoolBase {
37
37
  allowH2,
38
38
  ...options
39
39
  } = {}) {
40
- super()
41
-
42
40
  if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
43
41
  throw new InvalidArgumentError('invalid connections')
44
42
  }
@@ -51,6 +49,8 @@ class Pool extends PoolBase {
51
49
  throw new InvalidArgumentError('connect must be a function or an object')
52
50
  }
53
51
 
52
+ super()
53
+
54
54
  if (typeof connect !== 'function') {
55
55
  connect = buildConnector({
56
56
  ...tls,
@@ -63,9 +63,6 @@ class Pool extends PoolBase {
63
63
  })
64
64
  }
65
65
 
66
- this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
67
- ? options.interceptors.Pool
68
- : []
69
66
  this[kConnections] = connections || null
70
67
  this[kUrl] = util.parseOrigin(origin)
71
68
  this[kOptions] = { ...util.deepClone(options), connect, allowH2 }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { kProxy, kClose, kDestroy, kInterceptors } = require('../core/symbols')
3
+ const { kProxy, kClose, kDestroy } = require('../core/symbols')
4
4
  const { URL } = require('node:url')
5
5
  const Agent = require('./agent')
6
6
  const Pool = require('./pool')
@@ -23,10 +23,10 @@ function defaultFactory (origin, opts) {
23
23
  return new Pool(origin, opts)
24
24
  }
25
25
 
26
+ const noop = () => {}
27
+
26
28
  class ProxyAgent extends DispatcherBase {
27
29
  constructor (opts) {
28
- super()
29
-
30
30
  if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
31
31
  throw new InvalidArgumentError('Proxy uri is mandatory')
32
32
  }
@@ -36,13 +36,12 @@ class ProxyAgent extends DispatcherBase {
36
36
  throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
37
37
  }
38
38
 
39
+ super()
40
+
39
41
  const url = this.#getUrl(opts)
40
42
  const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
41
43
 
42
44
  this[kProxy] = { uri: href, protocol }
43
- this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent)
44
- ? opts.interceptors.ProxyAgent
45
- : []
46
45
  this[kRequestTls] = opts.requestTls
47
46
  this[kProxyTls] = opts.proxyTls
48
47
  this[kProxyHeaders] = opts.headers || {}
@@ -81,7 +80,7 @@ class ProxyAgent extends DispatcherBase {
81
80
  servername: this[kProxyTls]?.servername || proxyHostname
82
81
  })
83
82
  if (statusCode !== 200) {
84
- socket.on('error', () => {}).destroy()
83
+ socket.on('error', noop).destroy()
85
84
  callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
86
85
  }
87
86
  if (opts.protocol !== 'https:') {
@@ -1,7 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
4
+
3
5
  module.exports = class DecoratorHandler {
4
6
  #handler
7
+ #onCompleteCalled = false
8
+ #onErrorCalled = false
5
9
 
6
10
  constructor (handler) {
7
11
  if (typeof handler !== 'object' || handler === null) {
@@ -15,30 +19,50 @@ module.exports = class DecoratorHandler {
15
19
  }
16
20
 
17
21
  onError (...args) {
22
+ this.#onErrorCalled = true
18
23
  return this.#handler.onError?.(...args)
19
24
  }
20
25
 
21
26
  onUpgrade (...args) {
27
+ assert(!this.#onCompleteCalled)
28
+ assert(!this.#onErrorCalled)
29
+
22
30
  return this.#handler.onUpgrade?.(...args)
23
31
  }
24
32
 
25
33
  onResponseStarted (...args) {
34
+ assert(!this.#onCompleteCalled)
35
+ assert(!this.#onErrorCalled)
36
+
26
37
  return this.#handler.onResponseStarted?.(...args)
27
38
  }
28
39
 
29
40
  onHeaders (...args) {
41
+ assert(!this.#onCompleteCalled)
42
+ assert(!this.#onErrorCalled)
43
+
30
44
  return this.#handler.onHeaders?.(...args)
31
45
  }
32
46
 
33
47
  onData (...args) {
48
+ assert(!this.#onCompleteCalled)
49
+ assert(!this.#onErrorCalled)
50
+
34
51
  return this.#handler.onData?.(...args)
35
52
  }
36
53
 
37
54
  onComplete (...args) {
55
+ assert(!this.#onCompleteCalled)
56
+ assert(!this.#onErrorCalled)
57
+
58
+ this.#onCompleteCalled = true
38
59
  return this.#handler.onComplete?.(...args)
39
60
  }
40
61
 
41
62
  onBodySent (...args) {
63
+ assert(!this.#onCompleteCalled)
64
+ assert(!this.#onErrorCalled)
65
+
42
66
  return this.#handler.onBodySent?.(...args)
43
67
  }
44
68
  }
@@ -24,12 +24,21 @@ class BodyAsyncIterable {
24
24
  }
25
25
 
26
26
  class RedirectHandler {
27
+ static buildDispatch (dispatcher, maxRedirections) {
28
+ if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
29
+ throw new InvalidArgumentError('maxRedirections must be a positive number')
30
+ }
31
+
32
+ const dispatch = dispatcher.dispatch.bind(dispatcher)
33
+ return (opts, originalHandler) => dispatch(opts, new RedirectHandler(dispatch, maxRedirections, opts, originalHandler))
34
+ }
35
+
27
36
  constructor (dispatch, maxRedirections, opts, handler) {
28
37
  if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
29
38
  throw new InvalidArgumentError('maxRedirections must be a positive number')
30
39
  }
31
40
 
32
- util.validateHandler(handler, opts.method, opts.upgrade)
41
+ util.assertRequestHandler(handler, opts.method, opts.upgrade)
33
42
 
34
43
  this.dispatch = dispatch
35
44
  this.location = null
@@ -137,7 +146,7 @@ class RedirectHandler {
137
146
 
138
147
  TLDR: undici always ignores 3xx response bodies.
139
148
 
140
- Redirection is used to serve the requested resource from another URL, so it is assumes that
149
+ Redirection is used to serve the requested resource from another URL, so it assumes that
141
150
  no body is generated (and thus can be ignored). Even though generating a body is not prohibited.
142
151
 
143
152
  For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually
@@ -194,8 +194,8 @@ class RetryHandler {
194
194
 
195
195
  // Only Partial Content 206 supposed to provide Content-Range,
196
196
  // any other status code that partially consumed the payload
197
- // should not be retry because it would result in downstream
198
- // wrongly concatanete multiple responses.
197
+ // should not be retried because it would result in downstream
198
+ // wrongly concatenate multiple responses.
199
199
  if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
200
200
  this.abort(
201
201
  new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
@@ -281,7 +281,11 @@ class RetryHandler {
281
281
  // Weak etags are not useful for comparison nor cache
282
282
  // for instance not safe to assume if the response is byte-per-byte
283
283
  // equal
284
- if (this.etag != null && this.etag.startsWith('W/')) {
284
+ if (
285
+ this.etag != null &&
286
+ this.etag[0] === 'W' &&
287
+ this.etag[1] === '/'
288
+ ) {
285
289
  this.etag = null
286
290
  }
287
291
 
@@ -339,6 +343,11 @@ class RetryHandler {
339
343
  onRetry.bind(this)
340
344
  )
341
345
 
346
+ /**
347
+ * @this {RetryHandler}
348
+ * @param {Error} [err]
349
+ * @returns
350
+ */
342
351
  function onRetry (err) {
343
352
  if (err != null || this.aborted || isDisturbed(this.opts.body)) {
344
353
  return this.handler.onError(err)