undici 5.28.2 → 6.0.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.
package/README.md CHANGED
@@ -180,8 +180,6 @@ Implements [fetch](https://fetch.spec.whatwg.org/#fetch-method).
180
180
  * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
181
181
  * https://fetch.spec.whatwg.org/#fetch-method
182
182
 
183
- Only supported on Node 16.8+.
184
-
185
183
  Basic usage example:
186
184
 
187
185
  ```js
@@ -50,7 +50,7 @@ Arguments:
50
50
 
51
51
  ### `BalancedPool.removeUpstream(upstream)`
52
52
 
53
- Removes an upstream that was previously addded.
53
+ Removes an upstream that was previously added.
54
54
 
55
55
  ### `BalancedPool.close([callback])`
56
56
 
package/index.js CHANGED
@@ -98,58 +98,54 @@ function makeDispatcher (fn) {
98
98
  module.exports.setGlobalDispatcher = setGlobalDispatcher
99
99
  module.exports.getGlobalDispatcher = getGlobalDispatcher
100
100
 
101
- if (util.nodeMajor > 16 || (util.nodeMajor === 16 && util.nodeMinor >= 8)) {
102
- let fetchImpl = null
103
- module.exports.fetch = async function fetch (resource) {
104
- if (!fetchImpl) {
105
- fetchImpl = require('./lib/fetch').fetch
106
- }
107
-
108
- try {
109
- return await fetchImpl(...arguments)
110
- } catch (err) {
111
- if (typeof err === 'object') {
112
- Error.captureStackTrace(err, this)
113
- }
101
+ let fetchImpl = null
102
+ module.exports.fetch = async function fetch (resource) {
103
+ if (!fetchImpl) {
104
+ fetchImpl = require('./lib/fetch').fetch
105
+ }
114
106
 
115
- throw err
107
+ try {
108
+ return await fetchImpl(...arguments)
109
+ } catch (err) {
110
+ if (typeof err === 'object') {
111
+ Error.captureStackTrace(err, this)
116
112
  }
113
+
114
+ throw err
117
115
  }
118
- module.exports.Headers = require('./lib/fetch/headers').Headers
119
- module.exports.Response = require('./lib/fetch/response').Response
120
- module.exports.Request = require('./lib/fetch/request').Request
121
- module.exports.FormData = require('./lib/fetch/formdata').FormData
122
- module.exports.File = require('./lib/fetch/file').File
123
- module.exports.FileReader = require('./lib/fileapi/filereader').FileReader
116
+ }
117
+ module.exports.Headers = require('./lib/fetch/headers').Headers
118
+ module.exports.Response = require('./lib/fetch/response').Response
119
+ module.exports.Request = require('./lib/fetch/request').Request
120
+ module.exports.FormData = require('./lib/fetch/formdata').FormData
121
+ module.exports.File = require('./lib/fetch/file').File
122
+ module.exports.FileReader = require('./lib/fileapi/filereader').FileReader
124
123
 
125
- const { setGlobalOrigin, getGlobalOrigin } = require('./lib/fetch/global')
124
+ const { setGlobalOrigin, getGlobalOrigin } = require('./lib/fetch/global')
126
125
 
127
- module.exports.setGlobalOrigin = setGlobalOrigin
128
- module.exports.getGlobalOrigin = getGlobalOrigin
126
+ module.exports.setGlobalOrigin = setGlobalOrigin
127
+ module.exports.getGlobalOrigin = getGlobalOrigin
129
128
 
130
- const { CacheStorage } = require('./lib/cache/cachestorage')
131
- const { kConstruct } = require('./lib/cache/symbols')
129
+ const { CacheStorage } = require('./lib/cache/cachestorage')
130
+ const { kConstruct } = require('./lib/cache/symbols')
132
131
 
133
- // Cache & CacheStorage are tightly coupled with fetch. Even if it may run
134
- // in an older version of Node, it doesn't have any use without fetch.
135
- module.exports.caches = new CacheStorage(kConstruct)
136
- }
132
+ // Cache & CacheStorage are tightly coupled with fetch. Even if it may run
133
+ // in an older version of Node, it doesn't have any use without fetch.
134
+ module.exports.caches = new CacheStorage(kConstruct)
137
135
 
138
- if (util.nodeMajor >= 16) {
139
- const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies')
136
+ const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies')
140
137
 
141
- module.exports.deleteCookie = deleteCookie
142
- module.exports.getCookies = getCookies
143
- module.exports.getSetCookies = getSetCookies
144
- module.exports.setCookie = setCookie
138
+ module.exports.deleteCookie = deleteCookie
139
+ module.exports.getCookies = getCookies
140
+ module.exports.getSetCookies = getSetCookies
141
+ module.exports.setCookie = setCookie
145
142
 
146
- const { parseMIMEType, serializeAMimeType } = require('./lib/fetch/dataURL')
143
+ const { parseMIMEType, serializeAMimeType } = require('./lib/fetch/dataURL')
147
144
 
148
- module.exports.parseMIMEType = parseMIMEType
149
- module.exports.serializeAMimeType = serializeAMimeType
150
- }
145
+ module.exports.parseMIMEType = parseMIMEType
146
+ module.exports.serializeAMimeType = serializeAMimeType
151
147
 
152
- if (util.nodeMajor >= 18 && hasCrypto) {
148
+ if (hasCrypto) {
153
149
  const { WebSocket } = require('./lib/websocket/websocket')
154
150
 
155
151
  module.exports.WebSocket = WebSocket
package/lib/client.js CHANGED
@@ -249,7 +249,7 @@ class Client extends DispatcherBase {
249
249
  }
250
250
 
251
251
  if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== 'number' || maxConcurrentStreams < 1)) {
252
- throw new InvalidArgumentError('maxConcurrentStreams must be a possitive integer, greater than 0')
252
+ throw new InvalidArgumentError('maxConcurrentStreams must be a positive integer, greater than 0')
253
253
  }
254
254
 
255
255
  if (typeof connect !== 'function') {
@@ -296,7 +296,7 @@ class Client extends DispatcherBase {
296
296
  ? null
297
297
  : {
298
298
  // streams: null, // Fixed queue of streams - For future support of `push`
299
- openStreams: 0, // Keep track of them to decide wether or not unref the session
299
+ openStreams: 0, // Keep track of them to decide whether or not unref the session
300
300
  maxConcurrentStreams: maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server
301
301
  }
302
302
  this[kHost] = `${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}`
@@ -1706,7 +1706,7 @@ function writeH2 (client, session, request) {
1706
1706
  }
1707
1707
 
1708
1708
  // https://tools.ietf.org/html/rfc7540#section-8.3
1709
- // :path and :scheme headers must be omited when sending CONNECT
1709
+ // :path and :scheme headers must be omitted when sending CONNECT
1710
1710
 
1711
1711
  headers[HTTP2_HEADER_PATH] = path
1712
1712
  headers[HTTP2_HEADER_SCHEME] = 'https'
@@ -1833,7 +1833,7 @@ function writeH2 (client, session, request) {
1833
1833
  // })
1834
1834
 
1835
1835
  // stream.on('push', headers => {
1836
- // // TODO(HTTP/2): Suppor push
1836
+ // // TODO(HTTP/2): Support push
1837
1837
  // })
1838
1838
 
1839
1839
  // stream.on('trailers', headers => {
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- /* istanbul ignore file: only for Node 12 */
4
-
5
3
  const { kConnected, kSize } = require('../core/symbols')
6
4
 
7
5
  class CompatWeakRef {
@@ -41,8 +39,5 @@ module.exports = function () {
41
39
  FinalizationRegistry: CompatFinalizer
42
40
  }
43
41
  }
44
- return {
45
- WeakRef: global.WeakRef || CompatWeakRef,
46
- FinalizationRegistry: global.FinalizationRegistry || CompatFinalizer
47
- }
42
+ return { WeakRef, FinalizationRegistry }
48
43
  }
@@ -183,7 +183,11 @@ function setupTimeout (onConnectTimeout, timeout) {
183
183
  }
184
184
 
185
185
  function onConnectTimeout (socket) {
186
- util.destroy(socket, new ConnectTimeoutError())
186
+ let message = 'Connect Timeout Error'
187
+ if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
188
+ message = +` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')})`
189
+ }
190
+ util.destroy(socket, new ConnectTimeoutError(message))
187
191
  }
188
192
 
189
193
  module.exports = buildConnector
@@ -0,0 +1,116 @@
1
+ /** @type {Record<string, string | undefined>} */
2
+ const headerNameLowerCasedRecord = {}
3
+
4
+ // https://developer.mozilla.org/docs/Web/HTTP/Headers
5
+ const wellknownHeaderNames = [
6
+ 'Accept',
7
+ 'Accept-Encoding',
8
+ 'Accept-Language',
9
+ 'Accept-Ranges',
10
+ 'Access-Control-Allow-Credentials',
11
+ 'Access-Control-Allow-Headers',
12
+ 'Access-Control-Allow-Methods',
13
+ 'Access-Control-Allow-Origin',
14
+ 'Access-Control-Expose-Headers',
15
+ 'Access-Control-Max-Age',
16
+ 'Access-Control-Request-Headers',
17
+ 'Access-Control-Request-Method',
18
+ 'Age',
19
+ 'Allow',
20
+ 'Alt-Svc',
21
+ 'Alt-Used',
22
+ 'Authorization',
23
+ 'Cache-Control',
24
+ 'Clear-Site-Data',
25
+ 'Connection',
26
+ 'Content-Disposition',
27
+ 'Content-Encoding',
28
+ 'Content-Language',
29
+ 'Content-Length',
30
+ 'Content-Location',
31
+ 'Content-Range',
32
+ 'Content-Security-Policy',
33
+ 'Content-Security-Policy-Report-Only',
34
+ 'Content-Type',
35
+ 'Cookie',
36
+ 'Cross-Origin-Embedder-Policy',
37
+ 'Cross-Origin-Opener-Policy',
38
+ 'Cross-Origin-Resource-Policy',
39
+ 'Date',
40
+ 'Device-Memory',
41
+ 'Downlink',
42
+ 'ECT',
43
+ 'ETag',
44
+ 'Expect',
45
+ 'Expect-CT',
46
+ 'Expires',
47
+ 'Forwarded',
48
+ 'From',
49
+ 'Host',
50
+ 'If-Match',
51
+ 'If-Modified-Since',
52
+ 'If-None-Match',
53
+ 'If-Range',
54
+ 'If-Unmodified-Since',
55
+ 'Keep-Alive',
56
+ 'Last-Modified',
57
+ 'Link',
58
+ 'Location',
59
+ 'Max-Forwards',
60
+ 'Origin',
61
+ 'Permissions-Policy',
62
+ 'Pragma',
63
+ 'Proxy-Authenticate',
64
+ 'Proxy-Authorization',
65
+ 'RTT',
66
+ 'Range',
67
+ 'Referer',
68
+ 'Referrer-Policy',
69
+ 'Refresh',
70
+ 'Retry-After',
71
+ 'Sec-WebSocket-Accept',
72
+ 'Sec-WebSocket-Extensions',
73
+ 'Sec-WebSocket-Key',
74
+ 'Sec-WebSocket-Protocol',
75
+ 'Sec-WebSocket-Version',
76
+ 'Server',
77
+ 'Server-Timing',
78
+ 'Service-Worker-Allowed',
79
+ 'Service-Worker-Navigation-Preload',
80
+ 'Set-Cookie',
81
+ 'SourceMap',
82
+ 'Strict-Transport-Security',
83
+ 'Supports-Loading-Mode',
84
+ 'TE',
85
+ 'Timing-Allow-Origin',
86
+ 'Trailer',
87
+ 'Transfer-Encoding',
88
+ 'Upgrade',
89
+ 'Upgrade-Insecure-Requests',
90
+ 'User-Agent',
91
+ 'Vary',
92
+ 'Via',
93
+ 'WWW-Authenticate',
94
+ 'X-Content-Type-Options',
95
+ 'X-DNS-Prefetch-Control',
96
+ 'X-Frame-Options',
97
+ 'X-Permitted-Cross-Domain-Policies',
98
+ 'X-Powered-By',
99
+ 'X-Requested-With',
100
+ 'X-XSS-Protection'
101
+ ]
102
+
103
+ for (let i = 0; i < wellknownHeaderNames.length; ++i) {
104
+ const key = wellknownHeaderNames[i]
105
+ const lowerCasedKey = key.toLowerCase()
106
+ headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] =
107
+ lowerCasedKey
108
+ }
109
+
110
+ // Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
111
+ Object.setPrototypeOf(headerNameLowerCasedRecord, null)
112
+
113
+ module.exports = {
114
+ wellknownHeaderNames,
115
+ headerNameLowerCasedRecord
116
+ }
@@ -196,10 +196,6 @@ class Request {
196
196
  }
197
197
 
198
198
  if (util.isFormDataLike(this.body)) {
199
- if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) {
200
- throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.')
201
- }
202
-
203
199
  if (!extractBody) {
204
200
  extractBody = require('../fetch/body.js').extractBody
205
201
  }
package/lib/core/util.js CHANGED
@@ -9,6 +9,7 @@ const { InvalidArgumentError } = require('./errors')
9
9
  const { Blob } = require('buffer')
10
10
  const nodeUtil = require('util')
11
11
  const { stringify } = require('querystring')
12
+ const { headerNameLowerCasedRecord } = require('./constants')
12
13
 
13
14
  const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
14
15
 
@@ -223,19 +224,21 @@ function parseHeaders (headers, obj = {}) {
223
224
  if (!Array.isArray(headers)) return headers
224
225
 
225
226
  for (let i = 0; i < headers.length; i += 2) {
226
- const key = headers[i].toString().toLowerCase()
227
- let val = obj[key]
227
+ const key = headers[i].toString()
228
+ const lowerCasedKey = headerNameLowerCasedRecord[key] ?? key.toLowerCase()
229
+ let val = obj[lowerCasedKey]
228
230
 
229
231
  if (!val) {
230
- if (Array.isArray(headers[i + 1])) {
231
- obj[key] = headers[i + 1].map(x => x.toString('utf8'))
232
+ const headersValue = headers[i + 1]
233
+ if (typeof headersValue === 'string') {
234
+ obj[lowerCasedKey] = headersValue
232
235
  } else {
233
- obj[key] = headers[i + 1].toString('utf8')
236
+ obj[lowerCasedKey] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8')
234
237
  }
235
238
  } else {
236
239
  if (!Array.isArray(val)) {
237
240
  val = [val]
238
- obj[key] = val
241
+ obj[lowerCasedKey] = val
239
242
  }
240
243
  val.push(headers[i + 1].toString('utf8'))
241
244
  }
@@ -359,21 +362,9 @@ function getSocketInfo (socket) {
359
362
  }
360
363
  }
361
364
 
362
- async function * convertIterableToBuffer (iterable) {
363
- for await (const chunk of iterable) {
364
- yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
365
- }
366
- }
367
-
368
- let ReadableStream
365
+ /** @type {globalThis['ReadableStream']} */
369
366
  function ReadableStreamFrom (iterable) {
370
- if (!ReadableStream) {
371
- ReadableStream = require('stream/web').ReadableStream
372
- }
373
-
374
- if (ReadableStream.from) {
375
- return ReadableStream.from(convertIterableToBuffer(iterable))
376
- }
367
+ // We cannot use ReadableStream.from here because it does not return a byte stream.
377
368
 
378
369
  let iterator
379
370
  return new ReadableStream(
@@ -386,18 +377,21 @@ function ReadableStreamFrom (iterable) {
386
377
  if (done) {
387
378
  queueMicrotask(() => {
388
379
  controller.close()
380
+ controller.byobRequest?.respond(0)
389
381
  })
390
382
  } else {
391
383
  const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
392
- controller.enqueue(new Uint8Array(buf))
384
+ if (buf.byteLength) {
385
+ controller.enqueue(new Uint8Array(buf))
386
+ }
393
387
  }
394
388
  return controller.desiredSize > 0
395
389
  },
396
390
  async cancel (reason) {
397
391
  await iterator.return()
398
- }
399
- },
400
- 0
392
+ },
393
+ type: 'bytes'
394
+ }
401
395
  )
402
396
  }
403
397
 
package/lib/fetch/body.js CHANGED
@@ -13,7 +13,6 @@ const {
13
13
  const { FormData } = require('./formdata')
14
14
  const { kState } = require('./symbols')
15
15
  const { webidl } = require('./webidl')
16
- const { DOMException, structuredClone } = require('./constants')
17
16
  const { Blob, File: NativeFile } = require('buffer')
18
17
  const { kBodyUsed } = require('../core/symbols')
19
18
  const assert = require('assert')
@@ -22,8 +21,6 @@ const { isUint8Array, isArrayBuffer } = require('util/types')
22
21
  const { File: UndiciFile } = require('./file')
23
22
  const { parseMIMEType, serializeAMimeType } = require('./dataURL')
24
23
 
25
- let ReadableStream = globalThis.ReadableStream
26
-
27
24
  /** @type {globalThis['File']} */
28
25
  const File = NativeFile ?? UndiciFile
29
26
  const textEncoder = new TextEncoder()
@@ -31,10 +28,6 @@ const textDecoder = new TextDecoder()
31
28
 
32
29
  // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
33
30
  function extractBody (object, keepalive = false) {
34
- if (!ReadableStream) {
35
- ReadableStream = require('stream/web').ReadableStream
36
- }
37
-
38
31
  // 1. Let stream be null.
39
32
  let stream = null
40
33
 
@@ -47,16 +40,19 @@ function extractBody (object, keepalive = false) {
47
40
  stream = object.stream()
48
41
  } else {
49
42
  // 4. Otherwise, set stream to a new ReadableStream object, and set
50
- // up stream.
43
+ // up stream with byte reading support.
51
44
  stream = new ReadableStream({
52
45
  async pull (controller) {
53
- controller.enqueue(
54
- typeof source === 'string' ? textEncoder.encode(source) : source
55
- )
46
+ const buffer = typeof source === 'string' ? textEncoder.encode(source) : source
47
+
48
+ if (buffer.byteLength) {
49
+ controller.enqueue(buffer)
50
+ }
51
+
56
52
  queueMicrotask(() => readableStreamClose(controller))
57
53
  },
58
54
  start () {},
59
- type: undefined
55
+ type: 'bytes'
60
56
  })
61
57
  }
62
58
 
@@ -223,13 +219,17 @@ function extractBody (object, keepalive = false) {
223
219
  // When running action is done, close stream.
224
220
  queueMicrotask(() => {
225
221
  controller.close()
222
+ controller.byobRequest?.respond(0)
226
223
  })
227
224
  } else {
228
225
  // Whenever one or more bytes are available and stream is not errored,
229
226
  // enqueue a Uint8Array wrapping an ArrayBuffer containing the available
230
227
  // bytes into stream.
231
228
  if (!isErrored(stream)) {
232
- controller.enqueue(new Uint8Array(value))
229
+ const buffer = new Uint8Array(value)
230
+ if (buffer.byteLength) {
231
+ controller.enqueue(buffer)
232
+ }
233
233
  }
234
234
  }
235
235
  return controller.desiredSize > 0
@@ -237,7 +237,7 @@ function extractBody (object, keepalive = false) {
237
237
  async cancel (reason) {
238
238
  await iterator.return()
239
239
  },
240
- type: undefined
240
+ type: 'bytes'
241
241
  })
242
242
  }
243
243
 
@@ -251,11 +251,6 @@ function extractBody (object, keepalive = false) {
251
251
 
252
252
  // https://fetch.spec.whatwg.org/#bodyinit-safely-extract
253
253
  function safelyExtractBody (object, keepalive = false) {
254
- if (!ReadableStream) {
255
- // istanbul ignore next
256
- ReadableStream = require('stream/web').ReadableStream
257
- }
258
-
259
254
  // To safely extract a body and a `Content-Type` value from
260
255
  // a byte sequence or BodyInit object object, run these steps:
261
256
 
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { MessageChannel, receiveMessageOnPort } = require('worker_threads')
4
-
5
3
  const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
6
4
  const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
7
5
 
@@ -92,41 +90,7 @@ const subresource = [
92
90
  ]
93
91
  const subresourceSet = new Set(subresource)
94
92
 
95
- /** @type {globalThis['DOMException']} */
96
- const DOMException = globalThis.DOMException ?? (() => {
97
- // DOMException was only made a global in Node v17.0.0,
98
- // but fetch supports >= v16.8.
99
- try {
100
- atob('~')
101
- } catch (err) {
102
- return Object.getPrototypeOf(err).constructor
103
- }
104
- })()
105
-
106
- let channel
107
-
108
- /** @type {globalThis['structuredClone']} */
109
- const structuredClone =
110
- globalThis.structuredClone ??
111
- // https://github.com/nodejs/node/blob/b27ae24dcc4251bad726d9d84baf678d1f707fed/lib/internal/structured_clone.js
112
- // structuredClone was added in v17.0.0, but fetch supports v16.8
113
- function structuredClone (value, options = undefined) {
114
- if (arguments.length === 0) {
115
- throw new TypeError('missing argument')
116
- }
117
-
118
- if (!channel) {
119
- channel = new MessageChannel()
120
- }
121
- channel.port1.unref()
122
- channel.port2.unref()
123
- channel.port1.postMessage(value, options?.transfer)
124
- return receiveMessageOnPort(channel.port2).message
125
- }
126
-
127
93
  module.exports = {
128
- DOMException,
129
- structuredClone,
130
94
  subresource,
131
95
  forbiddenMethods,
132
96
  requestBodyHeader,
@@ -126,7 +126,13 @@ function URLSerializer (url, excludeFragment = false) {
126
126
  const href = url.href
127
127
  const hashLength = url.hash.length
128
128
 
129
- return hashLength === 0 ? href : href.substring(0, href.length - hashLength)
129
+ const serialized = hashLength === 0 ? href : href.substring(0, href.length - hashLength)
130
+
131
+ if (!hashLength && href.endsWith('#')) {
132
+ return serialized.slice(0, -1)
133
+ }
134
+
135
+ return serialized
130
136
  }
131
137
 
132
138
  // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
@@ -543,7 +549,7 @@ function serializeAMimeType (mimeType) {
543
549
  // 4. If value does not solely contain HTTP token code
544
550
  // points or value is the empty string, then:
545
551
  if (!HTTP_TOKEN_CODEPOINTS.test(value)) {
546
- // 1. Precede each occurence of U+0022 (") or
552
+ // 1. Precede each occurrence of U+0022 (") or
547
553
  // U+005C (\) in value with U+005C (\).
548
554
  value = value.replace(/(\\|")/g, '\\$1')
549
555