undici 7.14.0 → 7.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/docs/docs/api/Agent.md +1 -0
  3. package/docs/docs/api/Dispatcher.md +59 -0
  4. package/docs/docs/api/Errors.md +0 -1
  5. package/index-fetch.js +2 -2
  6. package/index.js +6 -9
  7. package/lib/api/api-request.js +22 -8
  8. package/lib/api/readable.js +7 -5
  9. package/lib/core/errors.js +217 -13
  10. package/lib/core/request.js +5 -1
  11. package/lib/core/util.js +45 -11
  12. package/lib/dispatcher/agent.js +44 -23
  13. package/lib/dispatcher/client-h1.js +20 -9
  14. package/lib/dispatcher/client-h2.js +13 -3
  15. package/lib/dispatcher/client.js +57 -57
  16. package/lib/dispatcher/dispatcher-base.js +12 -7
  17. package/lib/dispatcher/env-http-proxy-agent.js +12 -16
  18. package/lib/dispatcher/fixed-queue.js +15 -39
  19. package/lib/dispatcher/h2c-client.js +6 -6
  20. package/lib/dispatcher/pool-base.js +60 -43
  21. package/lib/dispatcher/pool.js +2 -2
  22. package/lib/dispatcher/proxy-agent.js +14 -9
  23. package/lib/global.js +19 -1
  24. package/lib/interceptor/cache.js +61 -0
  25. package/lib/interceptor/decompress.js +253 -0
  26. package/lib/llhttp/constants.d.ts +99 -1
  27. package/lib/llhttp/constants.js +34 -1
  28. package/lib/llhttp/llhttp-wasm.js +1 -1
  29. package/lib/llhttp/llhttp_simd-wasm.js +1 -1
  30. package/lib/llhttp/utils.d.ts +2 -2
  31. package/lib/llhttp/utils.js +3 -6
  32. package/lib/mock/mock-agent.js +4 -4
  33. package/lib/mock/mock-errors.js +10 -0
  34. package/lib/mock/mock-utils.js +12 -10
  35. package/lib/util/cache.js +6 -7
  36. package/lib/util/date.js +534 -140
  37. package/lib/web/cookies/index.js +1 -1
  38. package/lib/web/cookies/parse.js +2 -2
  39. package/lib/web/eventsource/eventsource-stream.js +2 -2
  40. package/lib/web/eventsource/eventsource.js +34 -29
  41. package/lib/web/eventsource/util.js +1 -9
  42. package/lib/web/fetch/body.js +20 -26
  43. package/lib/web/fetch/index.js +15 -16
  44. package/lib/web/fetch/response.js +2 -4
  45. package/lib/web/fetch/util.js +8 -230
  46. package/lib/web/subresource-integrity/Readme.md +9 -0
  47. package/lib/web/subresource-integrity/subresource-integrity.js +306 -0
  48. package/lib/web/webidl/index.js +203 -42
  49. package/lib/web/websocket/connection.js +4 -3
  50. package/lib/web/websocket/events.js +1 -1
  51. package/lib/web/websocket/stream/websocketerror.js +22 -1
  52. package/lib/web/websocket/stream/websocketstream.js +16 -7
  53. package/lib/web/websocket/websocket.js +32 -42
  54. package/package.json +9 -7
  55. package/types/agent.d.ts +1 -0
  56. package/types/diagnostics-channel.d.ts +0 -1
  57. package/types/errors.d.ts +5 -15
  58. package/types/interceptors.d.ts +5 -0
  59. package/types/snapshot-agent.d.ts +5 -3
  60. package/types/webidl.d.ts +82 -21
  61. package/lib/api/util.js +0 -95
  62. package/lib/llhttp/constants.js.map +0 -1
  63. package/lib/llhttp/utils.js.map +0 -1
package/lib/core/util.js CHANGED
@@ -102,13 +102,24 @@ function isBlobLike (object) {
102
102
  }
103
103
  }
104
104
 
105
+ /**
106
+ * @param {string} url The path to check for query strings or fragments.
107
+ * @returns {boolean} Returns true if the path contains a query string or fragment.
108
+ */
109
+ function pathHasQueryOrFragment (url) {
110
+ return (
111
+ url.includes('?') ||
112
+ url.includes('#')
113
+ )
114
+ }
115
+
105
116
  /**
106
117
  * @param {string} url The URL to add the query params to
107
118
  * @param {import('node:querystring').ParsedUrlQueryInput} queryParams The object to serialize into a URL query string
108
119
  * @returns {string} The URL with the query params added
109
120
  */
110
121
  function serializePathWithQuery (url, queryParams) {
111
- if (url.includes('?') || url.includes('#')) {
122
+ if (pathHasQueryOrFragment(url)) {
112
123
  throw new Error('Query params cannot be passed when url already contains "?" or "#".')
113
124
  }
114
125
 
@@ -598,12 +609,11 @@ function ReadableStreamFrom (iterable) {
598
609
  let iterator
599
610
  return new ReadableStream(
600
611
  {
601
- async start () {
612
+ start () {
602
613
  iterator = iterable[Symbol.asyncIterator]()
603
614
  },
604
615
  pull (controller) {
605
- async function pull () {
606
- const { done, value } = await iterator.next()
616
+ return iterator.next().then(({ done, value }) => {
607
617
  if (done) {
608
618
  queueMicrotask(() => {
609
619
  controller.close()
@@ -614,15 +624,13 @@ function ReadableStreamFrom (iterable) {
614
624
  if (buf.byteLength) {
615
625
  controller.enqueue(new Uint8Array(buf))
616
626
  } else {
617
- return await pull()
627
+ return this.pull(controller)
618
628
  }
619
629
  }
620
- }
621
-
622
- return pull()
630
+ })
623
631
  },
624
- async cancel () {
625
- await iterator.return()
632
+ cancel () {
633
+ return iterator.return()
626
634
  },
627
635
  type: 'bytes'
628
636
  }
@@ -868,6 +876,30 @@ function onConnectTimeout (socket, opts) {
868
876
  destroy(socket, new ConnectTimeoutError(message))
869
877
  }
870
878
 
879
+ /**
880
+ * @param {string} urlString
881
+ * @returns {string}
882
+ */
883
+ function getProtocolFromUrlString (urlString) {
884
+ if (
885
+ urlString[0] === 'h' &&
886
+ urlString[1] === 't' &&
887
+ urlString[2] === 't' &&
888
+ urlString[3] === 'p'
889
+ ) {
890
+ switch (urlString[4]) {
891
+ case ':':
892
+ return 'http:'
893
+ case 's':
894
+ if (urlString[5] === ':') {
895
+ return 'https:'
896
+ }
897
+ }
898
+ }
899
+ // fallback if none of the usual suspects
900
+ return urlString.slice(0, urlString.indexOf(':') + 1)
901
+ }
902
+
871
903
  const kEnumerableProperty = Object.create(null)
872
904
  kEnumerableProperty.enumerable = true
873
905
 
@@ -924,6 +956,7 @@ module.exports = {
924
956
  assertRequestHandler,
925
957
  getSocketInfo,
926
958
  isFormDataLike,
959
+ pathHasQueryOrFragment,
927
960
  serializePathWithQuery,
928
961
  addAbortListener,
929
962
  isValidHTTPToken,
@@ -938,5 +971,6 @@ module.exports = {
938
971
  nodeMinor,
939
972
  safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
940
973
  wrapRequestBody,
941
- setupConnectTimeout
974
+ setupConnectTimeout,
975
+ getProtocolFromUrlString
942
976
  }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { InvalidArgumentError } = require('../core/errors')
3
+ const { InvalidArgumentError, MaxOriginsReachedError } = require('../core/errors')
4
4
  const { kClients, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
5
5
  const DispatcherBase = require('./dispatcher-base')
6
6
  const Pool = require('./pool')
@@ -13,6 +13,7 @@ const kOnConnectionError = Symbol('onConnectionError')
13
13
  const kOnDrain = Symbol('onDrain')
14
14
  const kFactory = Symbol('factory')
15
15
  const kOptions = Symbol('options')
16
+ const kOrigins = Symbol('origins')
16
17
 
17
18
  function defaultFactory (origin, opts) {
18
19
  return opts && opts.connections === 1
@@ -21,7 +22,7 @@ function defaultFactory (origin, opts) {
21
22
  }
22
23
 
23
24
  class Agent extends DispatcherBase {
24
- constructor ({ factory = defaultFactory, connect, ...options } = {}) {
25
+ constructor ({ factory = defaultFactory, maxOrigins = Infinity, connect, ...options } = {}) {
25
26
  if (typeof factory !== 'function') {
26
27
  throw new InvalidArgumentError('factory must be a function.')
27
28
  }
@@ -30,42 +31,34 @@ class Agent extends DispatcherBase {
30
31
  throw new InvalidArgumentError('connect must be a function or an object')
31
32
  }
32
33
 
34
+ if (typeof maxOrigins !== 'number' || Number.isNaN(maxOrigins) || maxOrigins <= 0) {
35
+ throw new InvalidArgumentError('maxOrigins must be a number greater than 0')
36
+ }
37
+
33
38
  super()
34
39
 
35
40
  if (connect && typeof connect !== 'function') {
36
41
  connect = { ...connect }
37
42
  }
38
43
 
39
- this[kOptions] = { ...util.deepClone(options), connect }
44
+ this[kOptions] = { ...util.deepClone(options), maxOrigins, connect }
40
45
  this[kFactory] = factory
41
46
  this[kClients] = new Map()
47
+ this[kOrigins] = new Set()
42
48
 
43
49
  this[kOnDrain] = (origin, targets) => {
44
50
  this.emit('drain', origin, [this, ...targets])
45
51
  }
46
52
 
47
53
  this[kOnConnect] = (origin, targets) => {
48
- const result = this[kClients].get(origin)
49
- if (result) {
50
- result.count += 1
51
- }
52
54
  this.emit('connect', origin, [this, ...targets])
53
55
  }
54
56
 
55
57
  this[kOnDisconnect] = (origin, targets, err) => {
56
- const result = this[kClients].get(origin)
57
- if (result) {
58
- result.count -= 1
59
- if (result.count <= 0) {
60
- this[kClients].delete(origin)
61
- result.dispatcher.destroy()
62
- }
63
- }
64
58
  this.emit('disconnect', origin, [this, ...targets], err)
65
59
  }
66
60
 
67
61
  this[kOnConnectionError] = (origin, targets, err) => {
68
- // TODO: should this decrement result.count here?
69
62
  this.emit('connectionError', origin, [this, ...targets], err)
70
63
  }
71
64
  }
@@ -86,39 +79,67 @@ class Agent extends DispatcherBase {
86
79
  throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
87
80
  }
88
81
 
82
+ if (this[kOrigins].size >= this[kOptions].maxOrigins && !this[kOrigins].has(key)) {
83
+ throw new MaxOriginsReachedError()
84
+ }
85
+
89
86
  const result = this[kClients].get(key)
90
87
  let dispatcher = result && result.dispatcher
91
88
  if (!dispatcher) {
89
+ const closeClientIfUnused = (connected) => {
90
+ const result = this[kClients].get(key)
91
+ if (result) {
92
+ if (connected) result.count -= 1
93
+ if (result.count <= 0) {
94
+ this[kClients].delete(key)
95
+ result.dispatcher.close()
96
+ }
97
+ this[kOrigins].delete(key)
98
+ }
99
+ }
92
100
  dispatcher = this[kFactory](opts.origin, this[kOptions])
93
101
  .on('drain', this[kOnDrain])
94
- .on('connect', this[kOnConnect])
95
- .on('disconnect', this[kOnDisconnect])
96
- .on('connectionError', this[kOnConnectionError])
102
+ .on('connect', (origin, targets) => {
103
+ const result = this[kClients].get(key)
104
+ if (result) {
105
+ result.count += 1
106
+ }
107
+ this[kOnConnect](origin, targets)
108
+ })
109
+ .on('disconnect', (origin, targets, err) => {
110
+ closeClientIfUnused(true)
111
+ this[kOnDisconnect](origin, targets, err)
112
+ })
113
+ .on('connectionError', (origin, targets, err) => {
114
+ closeClientIfUnused(false)
115
+ this[kOnConnectionError](origin, targets, err)
116
+ })
97
117
 
98
118
  this[kClients].set(key, { count: 0, dispatcher })
119
+ this[kOrigins].add(key)
99
120
  }
100
121
 
101
122
  return dispatcher.dispatch(opts, handler)
102
123
  }
103
124
 
104
- async [kClose] () {
125
+ [kClose] () {
105
126
  const closePromises = []
106
127
  for (const { dispatcher } of this[kClients].values()) {
107
128
  closePromises.push(dispatcher.close())
108
129
  }
109
130
  this[kClients].clear()
110
131
 
111
- await Promise.all(closePromises)
132
+ return Promise.all(closePromises)
112
133
  }
113
134
 
114
- async [kDestroy] (err) {
135
+ [kDestroy] (err) {
115
136
  const destroyPromises = []
116
137
  for (const { dispatcher } of this[kClients].values()) {
117
138
  destroyPromises.push(dispatcher.destroy(err))
118
139
  }
119
140
  this[kClients].clear()
120
141
 
121
- await Promise.all(destroyPromises)
142
+ return Promise.all(destroyPromises)
122
143
  }
123
144
 
124
145
  get stats () {
@@ -64,11 +64,26 @@ function lazyllhttp () {
64
64
  const llhttpWasmData = process.env.JEST_WORKER_ID ? require('../llhttp/llhttp-wasm.js') : undefined
65
65
 
66
66
  let mod
67
- try {
68
- mod = new WebAssembly.Module(require('../llhttp/llhttp_simd-wasm.js'))
69
- } catch (e) {
70
- /* istanbul ignore next */
71
67
 
68
+ // We disable wasm SIMD on ppc64 as it seems to be broken on Power 9 architectures.
69
+ let useWasmSIMD = process.arch !== 'ppc64'
70
+ // The Env Variable UNDICI_NO_WASM_SIMD allows explicitly overriding the default behavior
71
+ if (process.env.UNDICI_NO_WASM_SIMD === '1') {
72
+ useWasmSIMD = true
73
+ } else if (process.env.UNDICI_NO_WASM_SIMD === '0') {
74
+ useWasmSIMD = false
75
+ }
76
+
77
+ if (useWasmSIMD) {
78
+ try {
79
+ mod = new WebAssembly.Module(require('../llhttp/llhttp_simd-wasm.js'))
80
+ /* istanbul ignore next */
81
+ } catch {
82
+ }
83
+ }
84
+
85
+ /* istanbul ignore next */
86
+ if (!mod) {
72
87
  // We could check if the error was caused by the simd option not
73
88
  // being enabled, but the occurring of this other error
74
89
  // * https://github.com/emscripten-core/emscripten/issues/11495
@@ -325,10 +340,6 @@ class Parser {
325
340
  currentBufferRef = chunk
326
341
  currentParser = this
327
342
  ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, chunk.length)
328
- /* eslint-disable-next-line no-useless-catch */
329
- } catch (err) {
330
- /* istanbul ignore next: difficult to make a test case for */
331
- throw err
332
343
  } finally {
333
344
  currentParser = null
334
345
  currentBufferRef = null
@@ -760,7 +771,7 @@ function onParserTimeout (parser) {
760
771
  * @param {import('net').Socket} socket
761
772
  * @returns
762
773
  */
763
- async function connectH1 (client, socket) {
774
+ function connectH1 (client, socket) {
764
775
  client[kSocket] = socket
765
776
 
766
777
  if (!llhttpInstance) {
@@ -77,7 +77,7 @@ function parseH2Headers (headers) {
77
77
  return result
78
78
  }
79
79
 
80
- async function connectH2 (client, socket) {
80
+ function connectH2 (client, socket) {
81
81
  client[kSocket] = socket
82
82
 
83
83
  const session = http2.connect(client[kUrl], {
@@ -279,7 +279,7 @@ function shouldSendContentLength (method) {
279
279
  function writeH2 (client, request) {
280
280
  const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]
281
281
  const session = client[kHTTP2Session]
282
- const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request
282
+ const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
283
283
  let { body } = request
284
284
 
285
285
  if (upgrade) {
@@ -292,6 +292,16 @@ function writeH2 (client, request) {
292
292
  const key = reqHeaders[n + 0]
293
293
  const val = reqHeaders[n + 1]
294
294
 
295
+ if (key === 'cookie') {
296
+ if (headers[key] != null) {
297
+ headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val]
298
+ } else {
299
+ headers[key] = val
300
+ }
301
+
302
+ continue
303
+ }
304
+
295
305
  if (Array.isArray(val)) {
296
306
  for (let i = 0; i < val.length; i++) {
297
307
  if (headers[key]) {
@@ -387,7 +397,7 @@ function writeH2 (client, request) {
387
397
  // :path and :scheme headers must be omitted when sending CONNECT
388
398
 
389
399
  headers[HTTP2_HEADER_PATH] = path
390
- headers[HTTP2_HEADER_SCHEME] = 'https'
400
+ headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
391
401
 
392
402
  // https://tools.ietf.org/html/rfc7231#section-4.3.1
393
403
  // https://tools.ietf.org/html/rfc7231#section-4.3.2
@@ -296,8 +296,7 @@ class Client extends DispatcherBase {
296
296
  }
297
297
 
298
298
  [kDispatch] (opts, handler) {
299
- const origin = opts.origin || this[kUrl].origin
300
- const request = new Request(origin, opts, handler)
299
+ const request = new Request(this[kUrl].origin, opts, handler)
301
300
 
302
301
  this[kQueue].push(request)
303
302
  if (this[kResuming]) {
@@ -317,7 +316,7 @@ class Client extends DispatcherBase {
317
316
  return this[kNeedDrain] < 2
318
317
  }
319
318
 
320
- async [kClose] () {
319
+ [kClose] () {
321
320
  // TODO: for H2 we need to gracefully flush the remaining enqueued
322
321
  // request and close each stream.
323
322
  return new Promise((resolve) => {
@@ -329,7 +328,7 @@ class Client extends DispatcherBase {
329
328
  })
330
329
  }
331
330
 
332
- async [kDestroy] (err) {
331
+ [kDestroy] (err) {
333
332
  return new Promise((resolve) => {
334
333
  const requests = this[kQueue].splice(this[kPendingIdx])
335
334
  for (let i = 0; i < requests.length; i++) {
@@ -381,9 +380,9 @@ function onError (client, err) {
381
380
 
382
381
  /**
383
382
  * @param {Client} client
384
- * @returns
383
+ * @returns {void}
385
384
  */
386
- async function connect (client) {
385
+ function connect (client) {
387
386
  assert(!client[kConnecting])
388
387
  assert(!client[kHTTPContext])
389
388
 
@@ -417,26 +416,23 @@ async function connect (client) {
417
416
  })
418
417
  }
419
418
 
420
- try {
421
- const socket = await new Promise((resolve, reject) => {
422
- client[kConnector]({
423
- host,
424
- hostname,
425
- protocol,
426
- port,
427
- servername: client[kServerName],
428
- localAddress: client[kLocalAddress]
429
- }, (err, socket) => {
430
- if (err) {
431
- reject(err)
432
- } else {
433
- resolve(socket)
434
- }
435
- })
436
- })
419
+ client[kConnector]({
420
+ host,
421
+ hostname,
422
+ protocol,
423
+ port,
424
+ servername: client[kServerName],
425
+ localAddress: client[kLocalAddress]
426
+ }, (err, socket) => {
427
+ if (err) {
428
+ handleConnectError(client, err, { host, hostname, protocol, port })
429
+ client[kResume]()
430
+ return
431
+ }
437
432
 
438
433
  if (client.destroyed) {
439
434
  util.destroy(socket.on('error', noop), new ClientDestroyedError())
435
+ client[kResume]()
440
436
  return
441
437
  }
442
438
 
@@ -444,11 +440,13 @@ async function connect (client) {
444
440
 
445
441
  try {
446
442
  client[kHTTPContext] = socket.alpnProtocol === 'h2'
447
- ? await connectH2(client, socket)
448
- : await connectH1(client, socket)
443
+ ? connectH2(client, socket)
444
+ : connectH1(client, socket)
449
445
  } catch (err) {
450
446
  socket.destroy().on('error', noop)
451
- throw err
447
+ handleConnectError(client, err, { host, hostname, protocol, port })
448
+ client[kResume]()
449
+ return
452
450
  }
453
451
 
454
452
  client[kConnecting] = false
@@ -473,44 +471,46 @@ async function connect (client) {
473
471
  socket
474
472
  })
475
473
  }
474
+
476
475
  client.emit('connect', client[kUrl], [client])
477
- } catch (err) {
478
- if (client.destroyed) {
479
- return
480
- }
476
+ client[kResume]()
477
+ })
478
+ }
481
479
 
482
- client[kConnecting] = false
480
+ function handleConnectError (client, err, { host, hostname, protocol, port }) {
481
+ if (client.destroyed) {
482
+ return
483
+ }
483
484
 
484
- if (channels.connectError.hasSubscribers) {
485
- channels.connectError.publish({
486
- connectParams: {
487
- host,
488
- hostname,
489
- protocol,
490
- port,
491
- version: client[kHTTPContext]?.version,
492
- servername: client[kServerName],
493
- localAddress: client[kLocalAddress]
494
- },
495
- connector: client[kConnector],
496
- error: err
497
- })
498
- }
485
+ client[kConnecting] = false
499
486
 
500
- if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
501
- assert(client[kRunning] === 0)
502
- while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
503
- const request = client[kQueue][client[kPendingIdx]++]
504
- util.errorRequest(client, request, err)
505
- }
506
- } else {
507
- onError(client, err)
508
- }
487
+ if (channels.connectError.hasSubscribers) {
488
+ channels.connectError.publish({
489
+ connectParams: {
490
+ host,
491
+ hostname,
492
+ protocol,
493
+ port,
494
+ version: client[kHTTPContext]?.version,
495
+ servername: client[kServerName],
496
+ localAddress: client[kLocalAddress]
497
+ },
498
+ connector: client[kConnector],
499
+ error: err
500
+ })
501
+ }
509
502
 
510
- client.emit('connectionError', client[kUrl], [client], err)
503
+ if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
504
+ assert(client[kRunning] === 0)
505
+ while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
506
+ const request = client[kQueue][client[kPendingIdx]++]
507
+ util.errorRequest(client, request, err)
508
+ }
509
+ } else {
510
+ onError(client, err)
511
511
  }
512
512
 
513
- client[kResume]()
513
+ client.emit('connectionError', client[kUrl], [client], err)
514
514
  }
515
515
 
516
516
  function emitDrain (client) {
@@ -13,19 +13,24 @@ const kOnDestroyed = Symbol('onDestroyed')
13
13
  const kOnClosed = Symbol('onClosed')
14
14
 
15
15
  class DispatcherBase extends Dispatcher {
16
- constructor () {
17
- super()
16
+ /** @type {boolean} */
17
+ [kDestroyed] = false;
18
18
 
19
- this[kDestroyed] = false
20
- this[kOnDestroyed] = null
21
- this[kClosed] = false
22
- this[kOnClosed] = []
23
- }
19
+ /** @type {Array|null} */
20
+ [kOnDestroyed] = null;
21
+
22
+ /** @type {boolean} */
23
+ [kClosed] = false;
24
+
25
+ /** @type {Array} */
26
+ [kOnClosed] = []
24
27
 
28
+ /** @returns {boolean} */
25
29
  get destroyed () {
26
30
  return this[kDestroyed]
27
31
  }
28
32
 
33
+ /** @returns {boolean} */
29
34
  get closed () {
30
35
  return this[kClosed]
31
36
  }
@@ -46,24 +46,20 @@ class EnvHttpProxyAgent extends DispatcherBase {
46
46
  return agent.dispatch(opts, handler)
47
47
  }
48
48
 
49
- async [kClose] () {
50
- await this[kNoProxyAgent].close()
51
- if (!this[kHttpProxyAgent][kClosed]) {
52
- await this[kHttpProxyAgent].close()
53
- }
54
- if (!this[kHttpsProxyAgent][kClosed]) {
55
- await this[kHttpsProxyAgent].close()
56
- }
49
+ [kClose] () {
50
+ return Promise.all([
51
+ this[kNoProxyAgent].close(),
52
+ !this[kHttpProxyAgent][kClosed] && this[kHttpProxyAgent].close(),
53
+ !this[kHttpsProxyAgent][kClosed] && this[kHttpsProxyAgent].close()
54
+ ])
57
55
  }
58
56
 
59
- async [kDestroy] (err) {
60
- await this[kNoProxyAgent].destroy(err)
61
- if (!this[kHttpProxyAgent][kDestroyed]) {
62
- await this[kHttpProxyAgent].destroy(err)
63
- }
64
- if (!this[kHttpsProxyAgent][kDestroyed]) {
65
- await this[kHttpsProxyAgent].destroy(err)
66
- }
57
+ [kDestroy] (err) {
58
+ return Promise.all([
59
+ this[kNoProxyAgent].destroy(err),
60
+ !this[kHttpProxyAgent][kDestroyed] && this[kHttpProxyAgent].destroy(err),
61
+ !this[kHttpsProxyAgent][kDestroyed] && this[kHttpsProxyAgent].destroy(err)
62
+ ])
67
63
  }
68
64
 
69
65
  #getProxyAgentForUrl (url) {
@@ -59,35 +59,21 @@ const kMask = kSize - 1
59
59
  * @template T
60
60
  */
61
61
  class FixedCircularBuffer {
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
79
- }
62
+ /** @type {number} */
63
+ bottom = 0
64
+ /** @type {number} */
65
+ top = 0
66
+ /** @type {Array<T|undefined>} */
67
+ list = new Array(kSize).fill(undefined)
68
+ /** @type {T|null} */
69
+ next = null
80
70
 
81
- /**
82
- * @returns {boolean}
83
- */
71
+ /** @returns {boolean} */
84
72
  isEmpty () {
85
73
  return this.top === this.bottom
86
74
  }
87
75
 
88
- /**
89
- * @returns {boolean}
90
- */
76
+ /** @returns {boolean} */
91
77
  isFull () {
92
78
  return ((this.top + 1) & kMask) === this.bottom
93
79
  }
@@ -101,9 +87,7 @@ class FixedCircularBuffer {
101
87
  this.top = (this.top + 1) & kMask
102
88
  }
103
89
 
104
- /**
105
- * @returns {T|null}
106
- */
90
+ /** @returns {T|null} */
107
91
  shift () {
108
92
  const nextItem = this.list[this.bottom]
109
93
  if (nextItem === undefined) { return null }
@@ -118,22 +102,16 @@ class FixedCircularBuffer {
118
102
  */
119
103
  module.exports = class FixedQueue {
120
104
  constructor () {
121
- /**
122
- * @type {FixedCircularBuffer<T>}
123
- */
105
+ /** @type {FixedCircularBuffer<T>} */
124
106
  this.head = this.tail = new FixedCircularBuffer()
125
107
  }
126
108
 
127
- /**
128
- * @returns {boolean}
129
- */
109
+ /** @returns {boolean} */
130
110
  isEmpty () {
131
111
  return this.head.isEmpty()
132
112
  }
133
113
 
134
- /**
135
- * @param {T} data
136
- */
114
+ /** @param {T} data */
137
115
  push (data) {
138
116
  if (this.head.isFull()) {
139
117
  // Head is full: Creates a new queue, sets the old queue's `.next` to it,
@@ -143,9 +121,7 @@ module.exports = class FixedQueue {
143
121
  this.head.push(data)
144
122
  }
145
123
 
146
- /**
147
- * @returns {T|null}
148
- */
124
+ /** @returns {T|null} */
149
125
  shift () {
150
126
  const tail = this.tail
151
127
  const next = tail.shift()