undici 7.0.0-alpha.1 → 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 (71) hide show
  1. package/README.md +2 -2
  2. package/docs/docs/api/Client.md +1 -1
  3. package/docs/docs/api/Debug.md +1 -1
  4. package/docs/docs/api/Dispatcher.md +53 -2
  5. package/docs/docs/api/MockAgent.md +2 -0
  6. package/docs/docs/api/MockPool.md +2 -1
  7. package/docs/docs/api/RetryAgent.md +1 -1
  8. package/docs/docs/api/RetryHandler.md +1 -1
  9. package/docs/docs/api/WebSocket.md +45 -3
  10. package/index.js +6 -2
  11. package/lib/api/abort-signal.js +2 -0
  12. package/lib/api/api-pipeline.js +4 -2
  13. package/lib/api/api-request.js +4 -2
  14. package/lib/api/api-stream.js +3 -1
  15. package/lib/api/api-upgrade.js +2 -2
  16. package/lib/api/readable.js +194 -41
  17. package/lib/api/util.js +2 -0
  18. package/lib/core/connect.js +49 -22
  19. package/lib/core/constants.js +11 -9
  20. package/lib/core/diagnostics.js +122 -128
  21. package/lib/core/request.js +4 -4
  22. package/lib/core/symbols.js +2 -0
  23. package/lib/core/tree.js +4 -2
  24. package/lib/core/util.js +220 -39
  25. package/lib/dispatcher/client-h1.js +299 -60
  26. package/lib/dispatcher/client-h2.js +1 -1
  27. package/lib/dispatcher/client.js +24 -7
  28. package/lib/dispatcher/fixed-queue.js +91 -49
  29. package/lib/dispatcher/pool-stats.js +2 -0
  30. package/lib/dispatcher/proxy-agent.js +3 -1
  31. package/lib/handler/redirect-handler.js +2 -2
  32. package/lib/handler/retry-handler.js +2 -2
  33. package/lib/interceptor/dns.js +346 -0
  34. package/lib/mock/mock-agent.js +5 -8
  35. package/lib/mock/mock-client.js +7 -2
  36. package/lib/mock/mock-errors.js +3 -1
  37. package/lib/mock/mock-interceptor.js +8 -6
  38. package/lib/mock/mock-pool.js +7 -2
  39. package/lib/mock/mock-symbols.js +2 -1
  40. package/lib/mock/mock-utils.js +33 -5
  41. package/lib/util/timers.js +50 -6
  42. package/lib/web/cache/cache.js +24 -21
  43. package/lib/web/cache/cachestorage.js +1 -1
  44. package/lib/web/cookies/index.js +6 -4
  45. package/lib/web/fetch/body.js +42 -34
  46. package/lib/web/fetch/constants.js +35 -26
  47. package/lib/web/fetch/formdata-parser.js +14 -3
  48. package/lib/web/fetch/formdata.js +40 -20
  49. package/lib/web/fetch/headers.js +116 -84
  50. package/lib/web/fetch/index.js +65 -59
  51. package/lib/web/fetch/request.js +130 -55
  52. package/lib/web/fetch/response.js +79 -36
  53. package/lib/web/fetch/util.js +104 -57
  54. package/lib/web/fetch/webidl.js +38 -14
  55. package/lib/web/websocket/connection.js +92 -15
  56. package/lib/web/websocket/constants.js +2 -3
  57. package/lib/web/websocket/events.js +4 -2
  58. package/lib/web/websocket/receiver.js +20 -26
  59. package/lib/web/websocket/stream/websocketerror.js +83 -0
  60. package/lib/web/websocket/stream/websocketstream.js +485 -0
  61. package/lib/web/websocket/util.js +115 -10
  62. package/lib/web/websocket/websocket.js +45 -170
  63. package/package.json +6 -6
  64. package/types/interceptors.d.ts +14 -0
  65. package/types/mock-agent.d.ts +3 -0
  66. package/types/readable.d.ts +10 -7
  67. package/types/webidl.d.ts +24 -4
  68. package/types/websocket.d.ts +33 -0
  69. package/lib/mock/pluralizer.js +0 -29
  70. package/lib/web/cache/symbols.js +0 -5
  71. package/lib/web/fetch/symbols.js +0 -8
@@ -19,7 +19,20 @@ const kBytesRead = Symbol('kBytesRead')
19
19
 
20
20
  const noop = () => {}
21
21
 
22
+ /**
23
+ * @class
24
+ * @extends {Readable}
25
+ * @see https://fetch.spec.whatwg.org/#body
26
+ */
22
27
  class BodyReadable extends Readable {
28
+ /**
29
+ * @param {object} opts
30
+ * @param {(this: Readable, size: number) => void} opts.resume
31
+ * @param {() => (void | null)} opts.abort
32
+ * @param {string} [opts.contentType = '']
33
+ * @param {number} [opts.contentLength]
34
+ * @param {number} [opts.highWaterMark = 64 * 1024]
35
+ */
23
36
  constructor ({
24
37
  resume,
25
38
  abort,
@@ -36,8 +49,15 @@ class BodyReadable extends Readable {
36
49
  this._readableState.dataEmitted = false
37
50
 
38
51
  this[kAbort] = abort
52
+
53
+ /**
54
+ * @type {Consume | null}
55
+ */
39
56
  this[kConsume] = null
40
57
  this[kBytesRead] = 0
58
+ /**
59
+ * @type {ReadableStream|null}
60
+ */
41
61
  this[kBody] = null
42
62
  this[kUsed] = false
43
63
  this[kContentType] = contentType
@@ -50,6 +70,11 @@ class BodyReadable extends Readable {
50
70
  this[kReading] = false
51
71
  }
52
72
 
73
+ /**
74
+ * @param {Error|null} err
75
+ * @param {(error:(Error|null)) => void} callback
76
+ * @returns {void}
77
+ */
53
78
  _destroy (err, callback) {
54
79
  if (!err && !this._readableState.endEmitted) {
55
80
  err = new RequestAbortedError()
@@ -61,7 +86,7 @@ class BodyReadable extends Readable {
61
86
 
62
87
  // Workaround for Node "bug". If the stream is destroyed in same
63
88
  // tick as it is created, then a user who is waiting for a
64
- // promise (i.e micro tick) for installing a 'error' listener will
89
+ // promise (i.e micro tick) for installing an 'error' listener will
65
90
  // never get a chance and will always encounter an unhandled exception.
66
91
  if (!this[kUsed]) {
67
92
  setImmediate(() => {
@@ -72,21 +97,36 @@ class BodyReadable extends Readable {
72
97
  }
73
98
  }
74
99
 
75
- on (ev, ...args) {
76
- if (ev === 'data' || ev === 'readable') {
100
+ /**
101
+ * @param {string} event
102
+ * @param {(...args: any[]) => void} listener
103
+ * @returns {this}
104
+ */
105
+ on (event, listener) {
106
+ if (event === 'data' || event === 'readable') {
77
107
  this[kReading] = true
78
108
  this[kUsed] = true
79
109
  }
80
- return super.on(ev, ...args)
110
+ return super.on(event, listener)
81
111
  }
82
112
 
83
- addListener (ev, ...args) {
84
- return this.on(ev, ...args)
113
+ /**
114
+ * @param {string} event
115
+ * @param {(...args: any[]) => void} listener
116
+ * @returns {this}
117
+ */
118
+ addListener (event, listener) {
119
+ return this.on(event, listener)
85
120
  }
86
121
 
87
- off (ev, ...args) {
88
- const ret = super.off(ev, ...args)
89
- if (ev === 'data' || ev === 'readable') {
122
+ /**
123
+ * @param {string|symbol} event
124
+ * @param {(...args: any[]) => void} listener
125
+ * @returns {this}
126
+ */
127
+ off (event, listener) {
128
+ const ret = super.off(event, listener)
129
+ if (event === 'data' || event === 'readable') {
90
130
  this[kReading] = (
91
131
  this.listenerCount('data') > 0 ||
92
132
  this.listenerCount('readable') > 0
@@ -95,10 +135,19 @@ class BodyReadable extends Readable {
95
135
  return ret
96
136
  }
97
137
 
98
- removeListener (ev, ...args) {
99
- return this.off(ev, ...args)
138
+ /**
139
+ * @param {string|symbol} event
140
+ * @param {(...args: any[]) => void} listener
141
+ * @returns {this}
142
+ */
143
+ removeListener (event, listener) {
144
+ return this.off(event, listener)
100
145
  }
101
146
 
147
+ /**
148
+ * @param {Buffer|null} chunk
149
+ * @returns {boolean}
150
+ */
102
151
  push (chunk) {
103
152
  this[kBytesRead] += chunk ? chunk.length : 0
104
153
 
@@ -109,43 +158,84 @@ class BodyReadable extends Readable {
109
158
  return super.push(chunk)
110
159
  }
111
160
 
112
- // https://fetch.spec.whatwg.org/#dom-body-text
161
+ /**
162
+ * Consumes and returns the body as a string.
163
+ *
164
+ * @see https://fetch.spec.whatwg.org/#dom-body-text
165
+ * @returns {Promise<string>}
166
+ */
113
167
  async text () {
114
168
  return consume(this, 'text')
115
169
  }
116
170
 
117
- // https://fetch.spec.whatwg.org/#dom-body-json
171
+ /**
172
+ * Consumes and returns the body as a JavaScript Object.
173
+ *
174
+ * @see https://fetch.spec.whatwg.org/#dom-body-json
175
+ * @returns {Promise<unknown>}
176
+ */
118
177
  async json () {
119
178
  return consume(this, 'json')
120
179
  }
121
180
 
122
- // https://fetch.spec.whatwg.org/#dom-body-blob
181
+ /**
182
+ * Consumes and returns the body as a Blob
183
+ *
184
+ * @see https://fetch.spec.whatwg.org/#dom-body-blob
185
+ * @returns {Promise<Blob>}
186
+ */
123
187
  async blob () {
124
188
  return consume(this, 'blob')
125
189
  }
126
190
 
127
- // https://fetch.spec.whatwg.org/#dom-body-bytes
191
+ /**
192
+ * Consumes and returns the body as an Uint8Array.
193
+ *
194
+ * @see https://fetch.spec.whatwg.org/#dom-body-bytes
195
+ * @returns {Promise<Uint8Array>}
196
+ */
128
197
  async bytes () {
129
198
  return consume(this, 'bytes')
130
199
  }
131
200
 
132
- // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
201
+ /**
202
+ * Consumes and returns the body as an ArrayBuffer.
203
+ *
204
+ * @see https://fetch.spec.whatwg.org/#dom-body-arraybuffer
205
+ * @returns {Promise<ArrayBuffer>}
206
+ */
133
207
  async arrayBuffer () {
134
208
  return consume(this, 'arrayBuffer')
135
209
  }
136
210
 
137
- // https://fetch.spec.whatwg.org/#dom-body-formdata
211
+ /**
212
+ * Not implemented
213
+ *
214
+ * @see https://fetch.spec.whatwg.org/#dom-body-formdata
215
+ * @throws {NotSupportedError}
216
+ */
138
217
  async formData () {
139
218
  // TODO: Implement.
140
219
  throw new NotSupportedError()
141
220
  }
142
221
 
143
- // https://fetch.spec.whatwg.org/#dom-body-bodyused
222
+ /**
223
+ * Returns true if the body is not null and the body has been consumed.
224
+ * Otherwise, returns false.
225
+ *
226
+ * @see https://fetch.spec.whatwg.org/#dom-body-bodyused
227
+ * @readonly
228
+ * @returns {boolean}
229
+ */
144
230
  get bodyUsed () {
145
231
  return util.isDisturbed(this)
146
232
  }
147
233
 
148
- // https://fetch.spec.whatwg.org/#dom-body-body
234
+ /**
235
+ * @see https://fetch.spec.whatwg.org/#dom-body-body
236
+ * @readonly
237
+ * @returns {ReadableStream}
238
+ */
149
239
  get body () {
150
240
  if (!this[kBody]) {
151
241
  this[kBody] = ReadableStreamFrom(this)
@@ -158,6 +248,13 @@ class BodyReadable extends Readable {
158
248
  return this[kBody]
159
249
  }
160
250
 
251
+ /**
252
+ * Dumps the response body by reading `limit` number of bytes.
253
+ * @param {object} opts
254
+ * @param {number} [opts.limit = 131072] Number of bytes to read.
255
+ * @param {AbortSignal} [opts.signal] An AbortSignal to cancel the dump.
256
+ * @returns {Promise<null>}
257
+ */
161
258
  async dump (opts) {
162
259
  const signal = opts?.signal
163
260
 
@@ -165,7 +262,9 @@ class BodyReadable extends Readable {
165
262
  throw new InvalidArgumentError('signal must be an AbortSignal')
166
263
  }
167
264
 
168
- const limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024
265
+ const limit = opts?.limit && Number.isFinite(opts.limit)
266
+ ? opts.limit
267
+ : 128 * 1024
169
268
 
170
269
  signal?.throwIfAborted()
171
270
 
@@ -174,26 +273,34 @@ class BodyReadable extends Readable {
174
273
  }
175
274
 
176
275
  return await new Promise((resolve, reject) => {
177
- if (this[kContentLength] > limit || this[kBytesRead] > limit) {
276
+ if (
277
+ (this[kContentLength] && (this[kContentLength] > limit)) ||
278
+ this[kBytesRead] > limit
279
+ ) {
178
280
  this.destroy(new AbortError())
179
281
  }
180
282
 
181
- const onAbort = () => {
182
- this.destroy(signal.reason ?? new AbortError())
283
+ if (signal) {
284
+ const onAbort = () => {
285
+ this.destroy(signal.reason ?? new AbortError())
286
+ }
287
+ signal.addEventListener('abort', onAbort)
288
+ this
289
+ .on('close', function () {
290
+ signal.removeEventListener('abort', onAbort)
291
+ if (signal.aborted) {
292
+ reject(signal.reason ?? new AbortError())
293
+ } else {
294
+ resolve(null)
295
+ }
296
+ })
297
+ } else {
298
+ this.on('close', resolve)
183
299
  }
184
- signal?.addEventListener('abort', onAbort)
185
300
 
186
301
  this
187
- .on('close', function () {
188
- signal?.removeEventListener('abort', onAbort)
189
- if (signal?.aborted) {
190
- reject(signal.reason ?? new AbortError())
191
- } else {
192
- resolve(null)
193
- }
194
- })
195
302
  .on('error', noop)
196
- .on('data', function (chunk) {
303
+ .on('data', () => {
197
304
  if (this[kBytesRead] > limit) {
198
305
  this.destroy()
199
306
  }
@@ -204,7 +311,7 @@ class BodyReadable extends Readable {
204
311
 
205
312
  /**
206
313
  * @param {BufferEncoding} encoding
207
- * @returns {BodyReadable}
314
+ * @returns {this}
208
315
  */
209
316
  setEncoding (encoding) {
210
317
  if (Buffer.isEncoding(encoding)) {
@@ -214,17 +321,40 @@ class BodyReadable extends Readable {
214
321
  }
215
322
  }
216
323
 
217
- // https://streams.spec.whatwg.org/#readablestream-locked
218
- function isLocked (self) {
324
+ /**
325
+ * @see https://streams.spec.whatwg.org/#readablestream-locked
326
+ * @param {BodyReadable} bodyReadable
327
+ * @returns {boolean}
328
+ */
329
+ function isLocked (bodyReadable) {
219
330
  // Consume is an implicit lock.
220
- return (self[kBody] && self[kBody].locked === true) || self[kConsume]
331
+ return bodyReadable[kBody]?.locked === true || bodyReadable[kConsume] !== null
221
332
  }
222
333
 
223
- // https://fetch.spec.whatwg.org/#body-unusable
224
- function isUnusable (self) {
225
- return util.isDisturbed(self) || isLocked(self)
334
+ /**
335
+ * @see https://fetch.spec.whatwg.org/#body-unusable
336
+ * @param {BodyReadable} bodyReadable
337
+ * @returns {boolean}
338
+ */
339
+ function isUnusable (bodyReadable) {
340
+ return util.isDisturbed(bodyReadable) || isLocked(bodyReadable)
226
341
  }
227
342
 
343
+ /**
344
+ * @typedef {object} Consume
345
+ * @property {string} type
346
+ * @property {BodyReadable} stream
347
+ * @property {((value?: any) => void)} resolve
348
+ * @property {((err: Error) => void)} reject
349
+ * @property {number} length
350
+ * @property {Buffer[]} body
351
+ */
352
+
353
+ /**
354
+ * @param {BodyReadable} stream
355
+ * @param {string} type
356
+ * @returns {Promise<any>}
357
+ */
228
358
  async function consume (stream, type) {
229
359
  assert(!stream[kConsume])
230
360
 
@@ -269,6 +399,10 @@ async function consume (stream, type) {
269
399
  })
270
400
  }
271
401
 
402
+ /**
403
+ * @param {Consume} consume
404
+ * @returns {void}
405
+ */
272
406
  function consumeStart (consume) {
273
407
  if (consume.body === null) {
274
408
  return
@@ -356,6 +490,11 @@ function chunksConcat (chunks, length) {
356
490
  return buffer
357
491
  }
358
492
 
493
+ /**
494
+ * @param {Consume} consume
495
+ * @param {BufferEncoding} encoding
496
+ * @returns {void}
497
+ */
359
498
  function consumeEnd (consume, encoding) {
360
499
  const { type, body, resolve, stream, length } = consume
361
500
 
@@ -378,11 +517,21 @@ function consumeEnd (consume, encoding) {
378
517
  }
379
518
  }
380
519
 
520
+ /**
521
+ * @param {Consume} consume
522
+ * @param {Buffer} chunk
523
+ * @returns {void}
524
+ */
381
525
  function consumePush (consume, chunk) {
382
526
  consume.length += chunk.length
383
527
  consume.body.push(chunk)
384
528
  }
385
529
 
530
+ /**
531
+ * @param {Consume} consume
532
+ * @param {Error} [err]
533
+ * @returns {void}
534
+ */
386
535
  function consumeFinish (consume, err) {
387
536
  if (consume.body === null) {
388
537
  return
@@ -394,6 +543,7 @@ function consumeFinish (consume, err) {
394
543
  consume.resolve()
395
544
  }
396
545
 
546
+ // Reset the consume object to allow for garbage collection.
397
547
  consume.type = null
398
548
  consume.stream = null
399
549
  consume.resolve = null
@@ -402,4 +552,7 @@ function consumeFinish (consume, err) {
402
552
  consume.body = null
403
553
  }
404
554
 
405
- module.exports = { Readable: BodyReadable, chunksDecode }
555
+ module.exports = {
556
+ Readable: BodyReadable,
557
+ chunksDecode
558
+ }
package/lib/api/util.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const assert = require('node:assert')
2
4
  const {
3
5
  ResponseStatusCodeError
@@ -6,6 +6,8 @@ const util = require('./util')
6
6
  const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
7
7
  const timers = require('../util/timers')
8
8
 
9
+ function noop () {}
10
+
9
11
  let tls // include tls conditionally since it is not always available
10
12
 
11
13
  // TODO: session re-use does not wait for the first
@@ -92,9 +94,11 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
92
94
  servername = servername || options.servername || util.getServerName(host) || null
93
95
 
94
96
  const sessionKey = servername || hostname
97
+ assert(sessionKey)
98
+
95
99
  const session = customSession || sessionCache.get(sessionKey) || null
96
100
 
97
- assert(sessionKey)
101
+ port = port || 443
98
102
 
99
103
  socket = tls.connect({
100
104
  highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
@@ -105,7 +109,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
105
109
  // TODO(HTTP/2): Add support for h2c
106
110
  ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'],
107
111
  socket: httpSocket, // upgrade socket connection
108
- port: port || 443,
112
+ port,
109
113
  host: hostname
110
114
  })
111
115
 
@@ -116,11 +120,14 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
116
120
  })
117
121
  } else {
118
122
  assert(!httpSocket, 'httpSocket can only be sent on TLS update')
123
+
124
+ port = port || 80
125
+
119
126
  socket = net.connect({
120
127
  highWaterMark: 64 * 1024, // Same as nodejs fs streams.
121
128
  ...options,
122
129
  localAddress,
123
- port: port || 80,
130
+ port,
124
131
  host: hostname
125
132
  })
126
133
  }
@@ -131,12 +138,12 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
131
138
  socket.setKeepAlive(true, keepAliveInitialDelay)
132
139
  }
133
140
 
134
- const cancelConnectTimeout = setupConnectTimeout(new WeakRef(socket), timeout)
141
+ const clearConnectTimeout = setupConnectTimeout(new WeakRef(socket), { timeout, hostname, port })
135
142
 
136
143
  socket
137
144
  .setNoDelay(true)
138
145
  .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
139
- cancelConnectTimeout()
146
+ queueMicrotask(clearConnectTimeout)
140
147
 
141
148
  if (callback) {
142
149
  const cb = callback
@@ -145,7 +152,7 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
145
152
  }
146
153
  })
147
154
  .on('error', function (err) {
148
- cancelConnectTimeout()
155
+ queueMicrotask(clearConnectTimeout)
149
156
 
150
157
  if (callback) {
151
158
  const cb = callback
@@ -158,50 +165,70 @@ function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, sess
158
165
  }
159
166
  }
160
167
 
168
+ /**
169
+ * @param {WeakRef<net.Socket>} socketWeakRef
170
+ * @param {object} opts
171
+ * @param {number} opts.timeout
172
+ * @param {string} opts.hostname
173
+ * @param {number} opts.port
174
+ * @returns {() => void}
175
+ */
161
176
  const setupConnectTimeout = process.platform === 'win32'
162
- ? (socket, timeout) => {
163
- if (!timeout) {
164
- return () => { }
177
+ ? (socketWeakRef, opts) => {
178
+ if (!opts.timeout) {
179
+ return noop
165
180
  }
166
181
 
167
182
  let s1 = null
168
183
  let s2 = null
169
- const timer = timers.setTimeout(() => {
184
+ const fastTimer = timers.setFastTimeout(() => {
170
185
  // setImmediate is added to make sure that we prioritize socket error events over timeouts
171
186
  s1 = setImmediate(() => {
172
187
  // Windows needs an extra setImmediate probably due to implementation differences in the socket logic
173
- s2 = setImmediate(() => onConnectTimeout(socket.deref()))
188
+ s2 = setImmediate(() => onConnectTimeout(socketWeakRef.deref(), opts))
174
189
  })
175
- }, timeout)
190
+ }, opts.timeout)
176
191
  return () => {
177
- timers.clearTimeout(timer)
192
+ timers.clearFastTimeout(fastTimer)
178
193
  clearImmediate(s1)
179
194
  clearImmediate(s2)
180
195
  }
181
196
  }
182
- : (socket, timeout) => {
183
- if (!timeout) {
184
- return () => { }
197
+ : (socketWeakRef, opts) => {
198
+ if (!opts.timeout) {
199
+ return noop
185
200
  }
186
201
 
187
202
  let s1 = null
188
- const timer = timers.setTimeout(() => {
203
+ const fastTimer = timers.setFastTimeout(() => {
189
204
  // setImmediate is added to make sure that we prioritize socket error events over timeouts
190
205
  s1 = setImmediate(() => {
191
- onConnectTimeout(socket.deref())
206
+ onConnectTimeout(socketWeakRef.deref(), opts)
192
207
  })
193
- }, timeout)
208
+ }, opts.timeout)
194
209
  return () => {
195
- timers.clearTimeout(timer)
210
+ timers.clearFastTimeout(fastTimer)
196
211
  clearImmediate(s1)
197
212
  }
198
213
  }
199
214
 
200
- function onConnectTimeout (socket) {
215
+ /**
216
+ * @param {net.Socket} socket
217
+ * @param {object} opts
218
+ * @param {number} opts.timeout
219
+ * @param {string} opts.hostname
220
+ * @param {number} opts.port
221
+ */
222
+ function onConnectTimeout (socket, opts) {
201
223
  let message = 'Connect Timeout Error'
202
224
  if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
203
- message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')})`
225
+ message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},`
226
+ } else {
227
+ message += ` (attempted address: ${opts.hostname}:${opts.port},`
204
228
  }
229
+
230
+ message += ` timeout: ${opts.timeout}ms)`
231
+
205
232
  util.destroy(socket, new ConnectTimeoutError(message))
206
233
  }
207
234
 
@@ -1,10 +1,9 @@
1
1
  'use strict'
2
2
 
3
- /** @type {Record<string, string | undefined>} */
4
- const headerNameLowerCasedRecord = {}
5
-
6
- // https://developer.mozilla.org/docs/Web/HTTP/Headers
7
- const wellknownHeaderNames = [
3
+ /**
4
+ * @see https://developer.mozilla.org/docs/Web/HTTP/Headers
5
+ */
6
+ const wellknownHeaderNames = /** @type {const} */ ([
8
7
  'Accept',
9
8
  'Accept-Encoding',
10
9
  'Accept-Language',
@@ -100,7 +99,13 @@ const wellknownHeaderNames = [
100
99
  'X-Powered-By',
101
100
  'X-Requested-With',
102
101
  'X-XSS-Protection'
103
- ]
102
+ ])
103
+
104
+ /** @type {Record<typeof wellknownHeaderNames[number]|Lowercase<typeof wellknownHeaderNames[number]>, string>} */
105
+ const headerNameLowerCasedRecord = {}
106
+
107
+ // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
108
+ Object.setPrototypeOf(headerNameLowerCasedRecord, null)
104
109
 
105
110
  for (let i = 0; i < wellknownHeaderNames.length; ++i) {
106
111
  const key = wellknownHeaderNames[i]
@@ -109,9 +114,6 @@ for (let i = 0; i < wellknownHeaderNames.length; ++i) {
109
114
  lowerCasedKey
110
115
  }
111
116
 
112
- // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
113
- Object.setPrototypeOf(headerNameLowerCasedRecord, null)
114
-
115
117
  module.exports = {
116
118
  wellknownHeaderNames,
117
119
  headerNameLowerCasedRecord