undici 6.6.2 → 6.7.1

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 (117) hide show
  1. package/README.md +49 -27
  2. package/docs/{api → docs/api}/DiagnosticsChannel.md +2 -2
  3. package/docs/{api → docs/api}/Dispatcher.md +39 -3
  4. package/docs/docs/api/Fetch.md +57 -0
  5. package/docs/{api → docs/api}/ProxyAgent.md +5 -1
  6. package/docs/docs/api/RetryAgent.md +45 -0
  7. package/docs/{api → docs/api}/RetryHandler.md +1 -1
  8. package/docs/{api → docs/api}/api-lifecycle.md +33 -4
  9. package/docs/{best-practices → docs/best-practices}/proxy.md +6 -6
  10. package/index-fetch.js +9 -8
  11. package/index.js +31 -25
  12. package/lib/api/api-request.js +1 -1
  13. package/lib/api/readable.js +12 -9
  14. package/lib/api/util.js +8 -6
  15. package/lib/core/request.js +72 -135
  16. package/lib/core/symbols.js +6 -5
  17. package/lib/core/tree.js +46 -26
  18. package/lib/core/util.js +41 -20
  19. package/lib/{agent.js → dispatcher/agent.js} +4 -4
  20. package/lib/{balanced-pool.js → dispatcher/balanced-pool.js} +3 -3
  21. package/lib/dispatcher/client-h1.js +1352 -0
  22. package/lib/dispatcher/client-h2.js +639 -0
  23. package/lib/dispatcher/client.js +611 -0
  24. package/lib/{dispatcher-base.js → dispatcher/dispatcher-base.js} +2 -2
  25. package/lib/{pool-base.js → dispatcher/pool-base.js} +3 -3
  26. package/lib/{pool-stats.js → dispatcher/pool-stats.js} +1 -1
  27. package/lib/{pool.js → dispatcher/pool.js} +4 -4
  28. package/lib/{proxy-agent.js → dispatcher/proxy-agent.js} +29 -35
  29. package/lib/dispatcher/retry-agent.js +35 -0
  30. package/lib/global.js +1 -1
  31. package/lib/handler/{RetryHandler.js → retry-handler.js} +2 -2
  32. package/lib/interceptor/{redirectInterceptor.js → redirect-interceptor.js} +1 -1
  33. package/lib/mock/mock-agent.js +2 -2
  34. package/lib/mock/mock-client.js +1 -1
  35. package/lib/mock/mock-interceptor.js +2 -2
  36. package/lib/mock/mock-pool.js +1 -1
  37. package/lib/mock/mock-utils.js +6 -4
  38. package/lib/{cache → web/cache}/cache.js +2 -4
  39. package/lib/{cache → web/cache}/cachestorage.js +1 -1
  40. package/lib/web/cache/symbols.js +5 -0
  41. package/lib/{cache → web/cache}/util.js +5 -9
  42. package/lib/{cookies → web/cookies}/parse.js +1 -1
  43. package/lib/{cookies → web/cookies}/util.js +76 -60
  44. package/lib/{eventsource → web/eventsource}/eventsource.js +2 -6
  45. package/lib/{fetch → web/fetch}/body.js +56 -175
  46. package/lib/{fetch/dataURL.js → web/fetch/data-url.js} +5 -2
  47. package/lib/{compat → web/fetch}/dispatcher-weakref.js +1 -1
  48. package/lib/{fetch → web/fetch}/file.js +7 -8
  49. package/lib/web/fetch/formdata-parser.js +488 -0
  50. package/lib/{fetch → web/fetch}/formdata.js +7 -68
  51. package/lib/{fetch → web/fetch}/headers.js +99 -71
  52. package/lib/{fetch → web/fetch}/index.js +33 -25
  53. package/lib/{fetch → web/fetch}/request.js +15 -7
  54. package/lib/{fetch → web/fetch}/response.js +3 -3
  55. package/lib/{fetch → web/fetch}/symbols.js +2 -1
  56. package/lib/{fetch → web/fetch}/util.js +171 -48
  57. package/lib/{fetch → web/fetch}/webidl.js +46 -16
  58. package/lib/{fileapi → web/fileapi}/filereader.js +1 -1
  59. package/lib/{fileapi → web/fileapi}/util.js +1 -1
  60. package/lib/{websocket → web/websocket}/connection.js +20 -10
  61. package/lib/{websocket → web/websocket}/constants.js +7 -0
  62. package/lib/{websocket → web/websocket}/events.js +1 -1
  63. package/lib/{websocket → web/websocket}/frame.js +1 -0
  64. package/lib/{websocket → web/websocket}/receiver.js +9 -16
  65. package/lib/{websocket → web/websocket}/util.js +37 -23
  66. package/lib/{websocket → web/websocket}/websocket.js +21 -9
  67. package/package.json +26 -54
  68. package/types/dispatcher.d.ts +1 -1
  69. package/types/fetch.d.ts +20 -21
  70. package/types/index.d.ts +2 -1
  71. package/types/retry-agent.d.ts +11 -0
  72. package/types/webidl.d.ts +6 -1
  73. package/docs/api/Fetch.md +0 -27
  74. package/docs/assets/lifecycle-diagram.png +0 -0
  75. package/lib/cache/symbols.js +0 -5
  76. package/lib/client.js +0 -2295
  77. package/lib/llhttp/llhttp.wasm +0 -0
  78. package/lib/llhttp/llhttp_simd.wasm +0 -0
  79. /package/docs/{api → docs/api}/Agent.md +0 -0
  80. /package/docs/{api → docs/api}/BalancedPool.md +0 -0
  81. /package/docs/{api → docs/api}/CacheStorage.md +0 -0
  82. /package/docs/{api → docs/api}/Client.md +0 -0
  83. /package/docs/{api → docs/api}/Connector.md +0 -0
  84. /package/docs/{api → docs/api}/ContentType.md +0 -0
  85. /package/docs/{api → docs/api}/Cookies.md +0 -0
  86. /package/docs/{api → docs/api}/Debug.md +0 -0
  87. /package/docs/{api → docs/api}/DispatchInterceptor.md +0 -0
  88. /package/docs/{api → docs/api}/Errors.md +0 -0
  89. /package/docs/{api → docs/api}/EventSource.md +0 -0
  90. /package/docs/{api → docs/api}/MockAgent.md +0 -0
  91. /package/docs/{api → docs/api}/MockClient.md +0 -0
  92. /package/docs/{api → docs/api}/MockErrors.md +0 -0
  93. /package/docs/{api → docs/api}/MockPool.md +0 -0
  94. /package/docs/{api → docs/api}/Pool.md +0 -0
  95. /package/docs/{api → docs/api}/PoolStats.md +0 -0
  96. /package/docs/{api → docs/api}/RedirectHandler.md +0 -0
  97. /package/docs/{api → docs/api}/Util.md +0 -0
  98. /package/docs/{api → docs/api}/WebSocket.md +0 -0
  99. /package/docs/{best-practices → docs/best-practices}/client-certificate.md +0 -0
  100. /package/docs/{best-practices → docs/best-practices}/mocking-request.md +0 -0
  101. /package/docs/{best-practices → docs/best-practices}/writing-tests.md +0 -0
  102. /package/lib/{dispatcher.js → dispatcher/dispatcher.js} +0 -0
  103. /package/lib/{node → dispatcher}/fixed-queue.js +0 -0
  104. /package/lib/handler/{DecoratorHandler.js → decorator-handler.js} +0 -0
  105. /package/lib/handler/{RedirectHandler.js → redirect-handler.js} +0 -0
  106. /package/lib/{timers.js → util/timers.js} +0 -0
  107. /package/lib/{cookies → web/cookies}/constants.js +0 -0
  108. /package/lib/{cookies → web/cookies}/index.js +0 -0
  109. /package/lib/{eventsource → web/eventsource}/eventsource-stream.js +0 -0
  110. /package/lib/{eventsource → web/eventsource}/util.js +0 -0
  111. /package/lib/{fetch → web/fetch}/LICENSE +0 -0
  112. /package/lib/{fetch → web/fetch}/constants.js +0 -0
  113. /package/lib/{fetch → web/fetch}/global.js +0 -0
  114. /package/lib/{fileapi → web/fileapi}/encoding.js +0 -0
  115. /package/lib/{fileapi → web/fileapi}/progressevent.js +0 -0
  116. /package/lib/{fileapi → web/fileapi}/symbols.js +0 -0
  117. /package/lib/{websocket → web/websocket}/symbols.js +0 -0
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const Busboy = require('@fastify/busboy')
4
- const util = require('../core/util')
3
+ const util = require('../../core/util')
5
4
  const {
6
5
  ReadableStreamFrom,
7
6
  isBlobLike,
@@ -9,23 +8,20 @@ const {
9
8
  readableStreamClose,
10
9
  createDeferredPromise,
11
10
  fullyReadBody,
12
- extractMimeType
11
+ extractMimeType,
12
+ utf8DecodeBytes
13
13
  } = require('./util')
14
14
  const { FormData } = require('./formdata')
15
15
  const { kState } = require('./symbols')
16
16
  const { webidl } = require('./webidl')
17
- const { Blob, File: NativeFile } = require('node:buffer')
18
- const { kBodyUsed } = require('../core/symbols')
17
+ const { Blob } = require('node:buffer')
19
18
  const assert = require('node:assert')
20
- const { isErrored } = require('../core/util')
21
- const { isUint8Array, isArrayBuffer } = require('util/types')
22
- const { File: UndiciFile } = require('./file')
23
- const { serializeAMimeType } = require('./dataURL')
19
+ const { isErrored } = require('../../core/util')
20
+ const { isArrayBuffer } = require('node:util/types')
21
+ const { serializeAMimeType } = require('./data-url')
22
+ const { multipartFormDataParser } = require('./formdata-parser')
24
23
 
25
- /** @type {globalThis['File']} */
26
- const File = NativeFile ?? UndiciFile
27
24
  const textEncoder = new TextEncoder()
28
- const textDecoder = new TextDecoder()
29
25
 
30
26
  // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
31
27
  function extractBody (object, keepalive = false) {
@@ -275,45 +271,18 @@ function cloneBody (body) {
275
271
 
276
272
  // 1. Let « out1, out2 » be the result of teeing body’s stream.
277
273
  const [out1, out2] = body.stream.tee()
278
- const out2Clone = structuredClone(out2, { transfer: [out2] })
279
- // This, for whatever reasons, unrefs out2Clone which allows
280
- // the process to exit by itself.
281
- const [, finalClone] = out2Clone.tee()
282
274
 
283
275
  // 2. Set body’s stream to out1.
284
276
  body.stream = out1
285
277
 
286
278
  // 3. Return a body whose stream is out2 and other members are copied from body.
287
279
  return {
288
- stream: finalClone,
280
+ stream: out2,
289
281
  length: body.length,
290
282
  source: body.source
291
283
  }
292
284
  }
293
285
 
294
- async function * consumeBody (body) {
295
- if (body) {
296
- if (isUint8Array(body)) {
297
- yield body
298
- } else {
299
- const stream = body.stream
300
-
301
- if (util.isDisturbed(stream)) {
302
- throw new TypeError('The body has already been consumed.')
303
- }
304
-
305
- if (stream.locked) {
306
- throw new TypeError('The stream is locked.')
307
- }
308
-
309
- // Compat.
310
- stream[kBodyUsed] = true
311
-
312
- yield * stream
313
- }
314
- }
315
- }
316
-
317
286
  function throwIfAborted (state) {
318
287
  if (state.aborted) {
319
288
  throw new DOMException('The operation was aborted.', 'AbortError')
@@ -328,7 +297,7 @@ function bodyMixinMethods (instance) {
328
297
  // given a byte sequence bytes: return a Blob whose
329
298
  // contents are bytes and whose type attribute is this’s
330
299
  // MIME type.
331
- return specConsumeBody(this, (bytes) => {
300
+ return consumeBody(this, (bytes) => {
332
301
  let mimeType = bodyMimeType(this)
333
302
 
334
303
  if (mimeType === null) {
@@ -348,7 +317,7 @@ function bodyMixinMethods (instance) {
348
317
  // of running consume body with this and the following step
349
318
  // given a byte sequence bytes: return a new ArrayBuffer
350
319
  // whose contents are bytes.
351
- return specConsumeBody(this, (bytes) => {
320
+ return consumeBody(this, (bytes) => {
352
321
  return new Uint8Array(bytes).buffer
353
322
  }, instance)
354
323
  },
@@ -356,127 +325,65 @@ function bodyMixinMethods (instance) {
356
325
  text () {
357
326
  // The text() method steps are to return the result of running
358
327
  // consume body with this and UTF-8 decode.
359
- return specConsumeBody(this, utf8DecodeBytes, instance)
328
+ return consumeBody(this, utf8DecodeBytes, instance)
360
329
  },
361
330
 
362
331
  json () {
363
332
  // The json() method steps are to return the result of running
364
333
  // consume body with this and parse JSON from bytes.
365
- return specConsumeBody(this, parseJSONFromBytes, instance)
334
+ return consumeBody(this, parseJSONFromBytes, instance)
366
335
  },
367
336
 
368
- async formData () {
369
- webidl.brandCheck(this, instance)
370
-
371
- throwIfAborted(this[kState])
372
-
373
- // 1. Let mimeType be the result of get the MIME type with this.
374
- const mimeType = bodyMimeType(this)
375
-
376
- // If mimeType’s essence is "multipart/form-data", then:
377
- if (mimeType !== null && mimeType.essence === 'multipart/form-data') {
378
- const headers = {}
379
- for (const [key, value] of this.headers) headers[key] = value
337
+ formData () {
338
+ // The formData() method steps are to return the result of running
339
+ // consume body with this and the following step given a byte sequence bytes:
340
+ return consumeBody(this, (value) => {
341
+ // 1. Let mimeType be the result of get the MIME type with this.
342
+ const mimeType = bodyMimeType(this)
343
+
344
+ // 2. If mimeType is non-null, then switch on mimeType’s essence and run
345
+ // the corresponding steps:
346
+ if (mimeType !== null) {
347
+ switch (mimeType.essence) {
348
+ case 'multipart/form-data': {
349
+ // 1. ... [long step]
350
+ const parsed = multipartFormDataParser(value, mimeType)
351
+
352
+ // 2. If that fails for some reason, then throw a TypeError.
353
+ if (parsed === 'failure') {
354
+ throw new TypeError('Failed to parse body as FormData.')
355
+ }
356
+
357
+ // 3. Return a new FormData object, appending each entry,
358
+ // resulting from the parsing operation, to its entry list.
359
+ const fd = new FormData()
360
+ fd[kState] = parsed
361
+
362
+ return fd
363
+ }
364
+ case 'application/x-www-form-urlencoded': {
365
+ // 1. Let entries be the result of parsing bytes.
366
+ const entries = new URLSearchParams(value.toString())
380
367
 
381
- const responseFormData = new FormData()
368
+ // 2. If entries is failure, then throw a TypeError.
382
369
 
383
- let busboy
370
+ // 3. Return a new FormData object whose entry list is entries.
371
+ const fd = new FormData()
384
372
 
385
- try {
386
- busboy = new Busboy({
387
- headers,
388
- preservePath: true
389
- })
390
- } catch (err) {
391
- throw new DOMException(`${err}`, 'AbortError')
392
- }
373
+ for (const [name, value] of entries) {
374
+ fd.append(name, value)
375
+ }
393
376
 
394
- busboy.on('field', (name, value) => {
395
- responseFormData.append(name, value)
396
- })
397
- busboy.on('file', (name, value, filename, encoding, mimeType) => {
398
- const chunks = []
399
-
400
- if (encoding === 'base64' || encoding.toLowerCase() === 'base64') {
401
- let base64chunk = ''
402
-
403
- value.on('data', (chunk) => {
404
- base64chunk += chunk.toString().replace(/[\r\n]/gm, '')
405
-
406
- const end = base64chunk.length - base64chunk.length % 4
407
- chunks.push(Buffer.from(base64chunk.slice(0, end), 'base64'))
408
-
409
- base64chunk = base64chunk.slice(end)
410
- })
411
- value.on('end', () => {
412
- chunks.push(Buffer.from(base64chunk, 'base64'))
413
- responseFormData.append(name, new File(chunks, filename, { type: mimeType }))
414
- })
415
- } else {
416
- value.on('data', (chunk) => {
417
- chunks.push(chunk)
418
- })
419
- value.on('end', () => {
420
- responseFormData.append(name, new File(chunks, filename, { type: mimeType }))
421
- })
422
- }
423
- })
424
-
425
- const busboyResolve = new Promise((resolve, reject) => {
426
- busboy.on('finish', resolve)
427
- busboy.on('error', (err) => reject(new TypeError(err)))
428
- })
429
-
430
- if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk)
431
- busboy.end()
432
- await busboyResolve
433
-
434
- return responseFormData
435
- } else if (mimeType !== null && mimeType.essence === 'application/x-www-form-urlencoded') {
436
- // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
437
-
438
- // 1. Let entries be the result of parsing bytes.
439
- let entries
440
- try {
441
- let text = ''
442
- // application/x-www-form-urlencoded parser will keep the BOM.
443
- // https://url.spec.whatwg.org/#concept-urlencoded-parser
444
- // Note that streaming decoder is stateful and cannot be reused
445
- const streamingDecoder = new TextDecoder('utf-8', { ignoreBOM: true })
446
-
447
- for await (const chunk of consumeBody(this[kState].body)) {
448
- if (!isUint8Array(chunk)) {
449
- throw new TypeError('Expected Uint8Array chunk')
377
+ return fd
450
378
  }
451
- text += streamingDecoder.decode(chunk, { stream: true })
452
379
  }
453
- text += streamingDecoder.decode()
454
- entries = new URLSearchParams(text)
455
- } catch (err) {
456
- // istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
457
- // 2. If entries is failure, then throw a TypeError.
458
- throw new TypeError(undefined, { cause: err })
459
380
  }
460
381
 
461
- // 3. Return a new FormData object whose entries are entries.
462
- const formData = new FormData()
463
- for (const [name, value] of entries) {
464
- formData.append(name, value)
465
- }
466
- return formData
467
- } else {
468
- // Wait a tick before checking if the request has been aborted.
469
- // Otherwise, a TypeError can be thrown when an AbortError should.
470
- await Promise.resolve()
471
-
472
- throwIfAborted(this[kState])
473
-
474
- // Otherwise, throw a TypeError.
475
- throw webidl.errors.exception({
476
- header: `${instance.name}.formData`,
477
- message: 'Could not parse content as FormData.'
478
- })
479
- }
382
+ // 3. Throw a TypeError.
383
+ throw new TypeError(
384
+ 'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".'
385
+ )
386
+ }, instance)
480
387
  }
481
388
  }
482
389
 
@@ -493,7 +400,7 @@ function mixinBody (prototype) {
493
400
  * @param {(value: unknown) => unknown} convertBytesToJSValue
494
401
  * @param {Response|Request} instance
495
402
  */
496
- async function specConsumeBody (object, convertBytesToJSValue, instance) {
403
+ async function consumeBody (object, convertBytesToJSValue, instance) {
497
404
  webidl.brandCheck(object, instance)
498
405
 
499
406
  throwIfAborted(object[kState])
@@ -545,32 +452,6 @@ function bodyUnusable (body) {
545
452
  return body != null && (body.stream.locked || util.isDisturbed(body.stream))
546
453
  }
547
454
 
548
- /**
549
- * @see https://encoding.spec.whatwg.org/#utf-8-decode
550
- * @param {Buffer} buffer
551
- */
552
- function utf8DecodeBytes (buffer) {
553
- if (buffer.length === 0) {
554
- return ''
555
- }
556
-
557
- // 1. Let buffer be the result of peeking three bytes from
558
- // ioQueue, converted to a byte sequence.
559
-
560
- // 2. If buffer is 0xEF 0xBB 0xBF, then read three
561
- // bytes from ioQueue. (Do nothing with those bytes.)
562
- if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
563
- buffer = buffer.subarray(3)
564
- }
565
-
566
- // 3. Process a queue with an instance of UTF-8’s
567
- // decoder, ioQueue, output, and "replacement".
568
- const output = textDecoder.decode(buffer)
569
-
570
- // 4. Return output.
571
- return output
572
- }
573
-
574
455
  /**
575
456
  * @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
576
457
  * @param {Uint8Array} bytes
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const assert = require('node:assert')
2
4
 
3
5
  const encoder = new TextEncoder()
@@ -626,7 +628,6 @@ function removeASCIIWhitespace (str, leading = true, trailing = true) {
626
628
  }
627
629
 
628
630
  /**
629
- *
630
631
  * @param {string} str
631
632
  * @param {boolean} leading
632
633
  * @param {boolean} trailing
@@ -736,5 +737,7 @@ module.exports = {
736
737
  collectAnHTTPQuotedString,
737
738
  serializeAMimeType,
738
739
  removeChars,
739
- minimizeSupportedMimeType
740
+ minimizeSupportedMimeType,
741
+ HTTP_TOKEN_CODEPOINTS,
742
+ isomorphicDecode
740
743
  }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { kConnected, kSize } = require('../core/symbols')
3
+ const { kConnected, kSize } = require('../../core/symbols')
4
4
 
5
5
  class CompatWeakRef {
6
6
  constructor (value) {
@@ -1,12 +1,14 @@
1
1
  'use strict'
2
2
 
3
+ const { EOL } = require('node:os')
3
4
  const { Blob, File: NativeFile } = require('node:buffer')
4
5
  const { types } = require('node:util')
5
6
  const { kState } = require('./symbols')
6
7
  const { isBlobLike } = require('./util')
7
8
  const { webidl } = require('./webidl')
8
- const { parseMIMEType, serializeAMimeType } = require('./dataURL')
9
- const { kEnumerableProperty } = require('../core/util')
9
+ const { parseMIMEType, serializeAMimeType } = require('./data-url')
10
+ const { kEnumerableProperty } = require('../../core/util')
11
+
10
12
  const encoder = new TextEncoder()
11
13
 
12
14
  class File extends Blob {
@@ -307,17 +309,14 @@ function processBlobParts (parts, options) {
307
309
  */
308
310
  function convertLineEndingsNative (s) {
309
311
  // 1. Let native line ending be be the code point U+000A LF.
310
- let nativeLineEnding = '\n'
311
-
312
312
  // 2. If the underlying platform’s conventions are to
313
313
  // represent newlines as a carriage return and line feed
314
314
  // sequence, set native line ending to the code point
315
315
  // U+000D CR followed by the code point U+000A LF.
316
- if (process.platform === 'win32') {
317
- nativeLineEnding = '\r\n'
318
- }
316
+ // NOTE: We are using the native line ending for the current
317
+ // platform, provided by node's os module.
319
318
 
320
- return s.replace(/\r?\n/g, nativeLineEnding)
319
+ return s.replace(/\r?\n/g, EOL)
321
320
  }
322
321
 
323
322
  // If this function is moved to ./util.js, some tools (such as