undici 7.10.0 → 7.12.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 (69) hide show
  1. package/README.md +159 -0
  2. package/docs/docs/api/CacheStore.md +3 -3
  3. package/docs/docs/api/Debug.md +13 -13
  4. package/docs/docs/api/DiagnosticsChannel.md +32 -4
  5. package/docs/docs/api/Dispatcher.md +22 -3
  6. package/docs/docs/api/GlobalInstallation.md +91 -0
  7. package/docs/docs/api/MockClient.md +4 -0
  8. package/docs/docs/api/MockPool.md +6 -0
  9. package/docs/docs/api/ProxyAgent.md +2 -0
  10. package/docs/docs/api/RetryAgent.md +6 -1
  11. package/docs/docs/api/RetryHandler.md +1 -0
  12. package/docs/docs/api/WebSocket.md +27 -0
  13. package/index.js +18 -1
  14. package/lib/api/api-stream.js +1 -1
  15. package/lib/api/readable.js +1 -3
  16. package/lib/cache/memory-cache-store.js +3 -3
  17. package/lib/cache/sqlite-cache-store.js +1 -1
  18. package/lib/core/connect.js +21 -51
  19. package/lib/core/diagnostics.js +6 -4
  20. package/lib/core/request.js +12 -1
  21. package/lib/core/tree.js +1 -1
  22. package/lib/core/util.js +0 -45
  23. package/lib/dispatcher/client-h1.js +9 -18
  24. package/lib/dispatcher/proxy-agent.js +2 -1
  25. package/lib/handler/cache-handler.js +4 -1
  26. package/lib/handler/redirect-handler.js +2 -2
  27. package/lib/handler/retry-handler.js +110 -56
  28. package/lib/interceptor/cache.js +2 -2
  29. package/lib/interceptor/redirect.js +1 -1
  30. package/lib/mock/mock-client.js +4 -0
  31. package/lib/mock/mock-pool.js +4 -0
  32. package/lib/util/cache.js +12 -2
  33. package/lib/util/promise.js +28 -0
  34. package/lib/util/timers.js +11 -9
  35. package/lib/web/cache/cache.js +11 -9
  36. package/lib/web/cache/cachestorage.js +1 -1
  37. package/lib/web/cookies/index.js +1 -1
  38. package/lib/web/eventsource/eventsource.js +3 -6
  39. package/lib/web/eventsource/util.js +1 -1
  40. package/lib/web/fetch/body.js +36 -24
  41. package/lib/web/fetch/formdata-parser.js +4 -4
  42. package/lib/web/fetch/formdata.js +1 -1
  43. package/lib/web/fetch/headers.js +1 -1
  44. package/lib/web/fetch/index.js +228 -226
  45. package/lib/web/fetch/request.js +16 -8
  46. package/lib/web/fetch/response.js +6 -4
  47. package/lib/web/fetch/util.js +23 -25
  48. package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
  49. package/lib/web/websocket/connection.js +4 -12
  50. package/lib/web/websocket/events.js +1 -1
  51. package/lib/web/websocket/frame.js +2 -1
  52. package/lib/web/websocket/receiver.js +2 -12
  53. package/lib/web/websocket/stream/websocketerror.js +1 -1
  54. package/lib/web/websocket/stream/websocketstream.js +8 -5
  55. package/lib/web/websocket/websocket.js +61 -5
  56. package/package.json +5 -5
  57. package/types/diagnostics-channel.d.ts +9 -0
  58. package/types/dispatcher.d.ts +3 -2
  59. package/types/env-http-proxy-agent.d.ts +2 -1
  60. package/types/eventsource.d.ts +3 -3
  61. package/types/fetch.d.ts +1 -0
  62. package/types/handlers.d.ts +1 -1
  63. package/types/mock-client.d.ts +2 -0
  64. package/types/mock-interceptor.d.ts +2 -0
  65. package/types/mock-pool.d.ts +2 -0
  66. package/types/retry-handler.d.ts +9 -0
  67. package/types/webidl.d.ts +29 -15
  68. package/types/websocket.d.ts +3 -1
  69. package/lib/web/fetch/dispatcher-weakref.js +0 -46
@@ -30,7 +30,6 @@ const {
30
30
  crossOriginResourcePolicyCheck,
31
31
  determineRequestsReferrer,
32
32
  coarsenedSharedCurrentTime,
33
- createDeferredPromise,
34
33
  sameOrigin,
35
34
  isCancelled,
36
35
  isAborted,
@@ -61,8 +60,9 @@ const { Readable, pipeline, finished, isErrored, isReadable } = require('node:st
61
60
  const { addAbortListener, bufferToLowerCasedHeaderName } = require('../../core/util')
62
61
  const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./data-url')
63
62
  const { getGlobalDispatcher } = require('../../global')
64
- const { webidl } = require('./webidl')
63
+ const { webidl } = require('../webidl')
65
64
  const { STATUS_CODES } = require('node:http')
65
+ const { createDeferredPromise } = require('../../util/promise')
66
66
  const GET_OR_HEAD = ['GET', 'HEAD']
67
67
 
68
68
  const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esbuildDetection !== 'undefined'
@@ -507,257 +507,258 @@ function fetching ({
507
507
  }
508
508
 
509
509
  // 16. Run main fetch given fetchParams.
510
- mainFetch(fetchParams)
511
- .catch(err => {
512
- fetchParams.controller.terminate(err)
513
- })
510
+ mainFetch(fetchParams, false)
514
511
 
515
512
  // 17. Return fetchParam's controller
516
513
  return fetchParams.controller
517
514
  }
518
515
 
519
516
  // https://fetch.spec.whatwg.org/#concept-main-fetch
520
- async function mainFetch (fetchParams, recursive = false) {
521
- // 1. Let request be fetchParams’s request.
522
- const request = fetchParams.request
523
-
524
- // 2. Let response be null.
525
- let response = null
517
+ async function mainFetch (fetchParams, recursive) {
518
+ try {
519
+ // 1. Let request be fetchParams’s request.
520
+ const request = fetchParams.request
526
521
 
527
- // 3. If request’s local-URLs-only flag is set and request’s current URL is
528
- // not local, then set response to a network error.
529
- if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
530
- response = makeNetworkError('local URLs only')
531
- }
522
+ // 2. Let response be null.
523
+ let response = null
532
524
 
533
- // 4. Run report Content Security Policy violations for request.
534
- // TODO
525
+ // 3. If request’s local-URLs-only flag is set and request’s current URL is
526
+ // not local, then set response to a network error.
527
+ if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) {
528
+ response = makeNetworkError('local URLs only')
529
+ }
535
530
 
536
- // 5. Upgrade request to a potentially trustworthy URL, if appropriate.
537
- tryUpgradeRequestToAPotentiallyTrustworthyURL(request)
531
+ // 4. Run report Content Security Policy violations for request.
532
+ // TODO
538
533
 
539
- // 6. If should request be blocked due to a bad port, should fetching request
540
- // be blocked as mixed content, or should request be blocked by Content
541
- // Security Policy returns blocked, then set response to a network error.
542
- if (requestBadPort(request) === 'blocked') {
543
- response = makeNetworkError('bad port')
544
- }
545
- // TODO: should fetching request be blocked as mixed content?
546
- // TODO: should request be blocked by Content Security Policy?
534
+ // 5. Upgrade request to a potentially trustworthy URL, if appropriate.
535
+ tryUpgradeRequestToAPotentiallyTrustworthyURL(request)
547
536
 
548
- // 7. If request’s referrer policy is the empty string, then set request’s
549
- // referrer policy to request’s policy container’s referrer policy.
550
- if (request.referrerPolicy === '') {
551
- request.referrerPolicy = request.policyContainer.referrerPolicy
552
- }
537
+ // 6. If should request be blocked due to a bad port, should fetching request
538
+ // be blocked as mixed content, or should request be blocked by Content
539
+ // Security Policy returns blocked, then set response to a network error.
540
+ if (requestBadPort(request) === 'blocked') {
541
+ response = makeNetworkError('bad port')
542
+ }
543
+ // TODO: should fetching request be blocked as mixed content?
544
+ // TODO: should request be blocked by Content Security Policy?
553
545
 
554
- // 8. If request’s referrer is not "no-referrer", then set request’s
555
- // referrer to the result of invoking determine request’s referrer.
556
- if (request.referrer !== 'no-referrer') {
557
- request.referrer = determineRequestsReferrer(request)
558
- }
546
+ // 7. If request’s referrer policy is the empty string, then set request’s
547
+ // referrer policy to request’s policy container’s referrer policy.
548
+ if (request.referrerPolicy === '') {
549
+ request.referrerPolicy = request.policyContainer.referrerPolicy
550
+ }
559
551
 
560
- // 9. Set request’s current URL’s scheme to "https" if all of the following
561
- // conditions are true:
562
- // - request’s current URL’s scheme is "http"
563
- // - request’s current URL’s host is a domain
564
- // - Matching request’s current URL’s host per Known HSTS Host Domain Name
565
- // Matching results in either a superdomain match with an asserted
566
- // includeSubDomains directive or a congruent match (with or without an
567
- // asserted includeSubDomains directive). [HSTS]
568
- // TODO
552
+ // 8. If request’s referrer is not "no-referrer", then set request’s
553
+ // referrer to the result of invoking determine request’s referrer.
554
+ if (request.referrer !== 'no-referrer') {
555
+ request.referrer = determineRequestsReferrer(request)
556
+ }
569
557
 
570
- // 10. If recursive is false, then run the remaining steps in parallel.
571
- // TODO
558
+ // 9. Set request’s current URL’s scheme to "https" if all of the following
559
+ // conditions are true:
560
+ // - request’s current URL’s scheme is "http"
561
+ // - request’s current URL’s host is a domain
562
+ // - Matching request’s current URL’s host per Known HSTS Host Domain Name
563
+ // Matching results in either a superdomain match with an asserted
564
+ // includeSubDomains directive or a congruent match (with or without an
565
+ // asserted includeSubDomains directive). [HSTS]
566
+ // TODO
572
567
 
573
- // 11. If response is null, then set response to the result of running
574
- // the steps corresponding to the first matching statement:
575
- if (response === null) {
576
- const currentURL = requestCurrentURL(request)
577
- if (
578
- // - request’s current URL’s origin is same origin with request’s origin,
579
- // and request’s response tainting is "basic"
580
- (sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') ||
581
- // request’s current URL’s scheme is "data"
582
- (currentURL.protocol === 'data:') ||
583
- // - request’s mode is "navigate" or "websocket"
584
- (request.mode === 'navigate' || request.mode === 'websocket')
585
- ) {
586
- // 1. Set request’s response tainting to "basic".
587
- request.responseTainting = 'basic'
588
-
589
- // 2. Return the result of running scheme fetch given fetchParams.
590
- response = await schemeFetch(fetchParams)
591
-
592
- // request’s mode is "same-origin"
593
- } else if (request.mode === 'same-origin') {
594
- // 1. Return a network error.
595
- response = makeNetworkError('request mode cannot be "same-origin"')
596
-
597
- // request’s mode is "no-cors"
598
- } else if (request.mode === 'no-cors') {
599
- // 1. If request’s redirect mode is not "follow", then return a network
600
- // error.
601
- if (request.redirect !== 'follow') {
602
- response = makeNetworkError(
603
- 'redirect mode cannot be "follow" for "no-cors" request'
604
- )
605
- } else {
606
- // 2. Set request’s response tainting to "opaque".
607
- request.responseTainting = 'opaque'
568
+ // 10. If recursive is false, then run the remaining steps in parallel.
569
+ // TODO
608
570
 
609
- // 3. Return the result of running scheme fetch given fetchParams.
571
+ // 11. If response is null, then set response to the result of running
572
+ // the steps corresponding to the first matching statement:
573
+ if (response === null) {
574
+ const currentURL = requestCurrentURL(request)
575
+ if (
576
+ // - request’s current URL’s origin is same origin with request’s origin,
577
+ // and request’s response tainting is "basic"
578
+ (sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') ||
579
+ // request’s current URL’s scheme is "data"
580
+ (currentURL.protocol === 'data:') ||
581
+ // - request’s mode is "navigate" or "websocket"
582
+ (request.mode === 'navigate' || request.mode === 'websocket')
583
+ ) {
584
+ // 1. Set request’s response tainting to "basic".
585
+ request.responseTainting = 'basic'
586
+
587
+ // 2. Return the result of running scheme fetch given fetchParams.
610
588
  response = await schemeFetch(fetchParams)
611
- }
612
- // request’s current URL’s scheme is not an HTTP(S) scheme
613
- } else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
614
- // Return a network error.
615
- response = makeNetworkError('URL scheme must be a HTTP(S) scheme')
616
-
617
- // - request’s use-CORS-preflight flag is set
618
- // - request’s unsafe-request flag is set and either request’s method is
619
- // not a CORS-safelisted method or CORS-unsafe request-header names with
620
- // request’s header list is not empty
621
- // 1. Set request’s response tainting to "cors".
622
- // 2. Let corsWithPreflightResponse be the result of running HTTP fetch
623
- // given fetchParams and true.
624
- // 3. If corsWithPreflightResponse is a network error, then clear cache
625
- // entries using request.
626
- // 4. Return corsWithPreflightResponse.
627
- // TODO
628
-
629
- // Otherwise
630
- } else {
631
- // 1. Set request’s response tainting to "cors".
632
- request.responseTainting = 'cors'
633
589
 
634
- // 2. Return the result of running HTTP fetch given fetchParams.
635
- response = await httpFetch(fetchParams)
636
- }
637
- }
590
+ // request’s mode is "same-origin"
591
+ } else if (request.mode === 'same-origin') {
592
+ // 1. Return a network error.
593
+ response = makeNetworkError('request mode cannot be "same-origin"')
594
+
595
+ // request’s mode is "no-cors"
596
+ } else if (request.mode === 'no-cors') {
597
+ // 1. If request’s redirect mode is not "follow", then return a network
598
+ // error.
599
+ if (request.redirect !== 'follow') {
600
+ response = makeNetworkError(
601
+ 'redirect mode cannot be "follow" for "no-cors" request'
602
+ )
603
+ } else {
604
+ // 2. Set request’s response tainting to "opaque".
605
+ request.responseTainting = 'opaque'
638
606
 
639
- // 12. If recursive is true, then return response.
640
- if (recursive) {
641
- return response
642
- }
643
-
644
- // 13. If response is not a network error and response is not a filtered
645
- // response, then:
646
- if (response.status !== 0 && !response.internalResponse) {
647
- // If request’s response tainting is "cors", then:
648
- if (request.responseTainting === 'cors') {
649
- // 1. Let headerNames be the result of extracting header list values
650
- // given `Access-Control-Expose-Headers` and response’s header list.
651
- // TODO
652
- // 2. If request’s credentials mode is not "include" and headerNames
653
- // contains `*`, then set response’s CORS-exposed header-name list to
654
- // all unique header names in response’s header list.
655
- // TODO
656
- // 3. Otherwise, if headerNames is not null or failure, then set
657
- // response’s CORS-exposed header-name list to headerNames.
658
- // TODO
607
+ // 3. Return the result of running scheme fetch given fetchParams.
608
+ response = await schemeFetch(fetchParams)
609
+ }
610
+ // request’s current URL’s scheme is not an HTTP(S) scheme
611
+ } else if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) {
612
+ // Return a network error.
613
+ response = makeNetworkError('URL scheme must be a HTTP(S) scheme')
614
+
615
+ // - request’s use-CORS-preflight flag is set
616
+ // - request’s unsafe-request flag is set and either request’s method is
617
+ // not a CORS-safelisted method or CORS-unsafe request-header names with
618
+ // request’s header list is not empty
619
+ // 1. Set request’s response tainting to "cors".
620
+ // 2. Let corsWithPreflightResponse be the result of running HTTP fetch
621
+ // given fetchParams and true.
622
+ // 3. If corsWithPreflightResponse is a network error, then clear cache
623
+ // entries using request.
624
+ // 4. Return corsWithPreflightResponse.
625
+ // TODO
626
+
627
+ // Otherwise
628
+ } else {
629
+ // 1. Set request’s response tainting to "cors".
630
+ request.responseTainting = 'cors'
631
+
632
+ // 2. Return the result of running HTTP fetch given fetchParams.
633
+ response = await httpFetch(fetchParams)
634
+ }
659
635
  }
660
636
 
661
- // Set response to the following filtered response with response as its
662
- // internal response, depending on request’s response tainting:
663
- if (request.responseTainting === 'basic') {
664
- response = filterResponse(response, 'basic')
665
- } else if (request.responseTainting === 'cors') {
666
- response = filterResponse(response, 'cors')
667
- } else if (request.responseTainting === 'opaque') {
668
- response = filterResponse(response, 'opaque')
669
- } else {
670
- assert(false)
637
+ // 12. If recursive is true, then return response.
638
+ if (recursive) {
639
+ return response
671
640
  }
672
- }
673
641
 
674
- // 14. Let internalResponse be response, if response is a network error,
675
- // and response’s internal response otherwise.
676
- let internalResponse =
677
- response.status === 0 ? response : response.internalResponse
642
+ // 13. If response is not a network error and response is not a filtered
643
+ // response, then:
644
+ if (response.status !== 0 && !response.internalResponse) {
645
+ // If request’s response tainting is "cors", then:
646
+ if (request.responseTainting === 'cors') {
647
+ // 1. Let headerNames be the result of extracting header list values
648
+ // given `Access-Control-Expose-Headers` and response’s header list.
649
+ // TODO
650
+ // 2. If request’s credentials mode is not "include" and headerNames
651
+ // contains `*`, then set response’s CORS-exposed header-name list to
652
+ // all unique header names in response’s header list.
653
+ // TODO
654
+ // 3. Otherwise, if headerNames is not null or failure, then set
655
+ // response’s CORS-exposed header-name list to headerNames.
656
+ // TODO
657
+ }
678
658
 
679
- // 15. If internalResponse’s URL list is empty, then set it to a clone of
680
- // request’s URL list.
681
- if (internalResponse.urlList.length === 0) {
682
- internalResponse.urlList.push(...request.urlList)
683
- }
659
+ // Set response to the following filtered response with response as its
660
+ // internal response, depending on request’s response tainting:
661
+ if (request.responseTainting === 'basic') {
662
+ response = filterResponse(response, 'basic')
663
+ } else if (request.responseTainting === 'cors') {
664
+ response = filterResponse(response, 'cors')
665
+ } else if (request.responseTainting === 'opaque') {
666
+ response = filterResponse(response, 'opaque')
667
+ } else {
668
+ assert(false)
669
+ }
670
+ }
684
671
 
685
- // 16. If request’s timing allow failed flag is unset, then set
686
- // internalResponse’s timing allow passed flag.
687
- if (!request.timingAllowFailed) {
688
- response.timingAllowPassed = true
689
- }
672
+ // 14. Let internalResponse be response, if response is a network error,
673
+ // and response’s internal response otherwise.
674
+ let internalResponse =
675
+ response.status === 0 ? response : response.internalResponse
690
676
 
691
- // 17. If response is not a network error and any of the following returns
692
- // blocked
693
- // - should internalResponse to request be blocked as mixed content
694
- // - should internalResponse to request be blocked by Content Security Policy
695
- // - should internalResponse to request be blocked due to its MIME type
696
- // - should internalResponse to request be blocked due to nosniff
697
- // TODO
677
+ // 15. If internalResponse’s URL list is empty, then set it to a clone of
678
+ // request’s URL list.
679
+ if (internalResponse.urlList.length === 0) {
680
+ internalResponse.urlList.push(...request.urlList)
681
+ }
698
682
 
699
- // 18. If response’s type is "opaque", internalResponse’s status is 206,
700
- // internalResponse’s range-requested flag is set, and request’s header
701
- // list does not contain `Range`, then set response and internalResponse
702
- // to a network error.
703
- if (
704
- response.type === 'opaque' &&
705
- internalResponse.status === 206 &&
706
- internalResponse.rangeRequested &&
707
- !request.headers.contains('range', true)
708
- ) {
709
- response = internalResponse = makeNetworkError()
710
- }
683
+ // 16. If request’s timing allow failed flag is unset, then set
684
+ // internalResponse’s timing allow passed flag.
685
+ if (!request.timingAllowFailed) {
686
+ response.timingAllowPassed = true
687
+ }
711
688
 
712
- // 19. If response is not a network error and either request’s method is
713
- // `HEAD` or `CONNECT`, or internalResponse’s status is a null body status,
714
- // set internalResponse’s body to null and disregard any enqueuing toward
715
- // it (if any).
716
- if (
717
- response.status !== 0 &&
718
- (request.method === 'HEAD' ||
719
- request.method === 'CONNECT' ||
720
- nullBodyStatus.includes(internalResponse.status))
721
- ) {
722
- internalResponse.body = null
723
- fetchParams.controller.dump = true
724
- }
689
+ // 17. If response is not a network error and any of the following returns
690
+ // blocked
691
+ // - should internalResponse to request be blocked as mixed content
692
+ // - should internalResponse to request be blocked by Content Security Policy
693
+ // - should internalResponse to request be blocked due to its MIME type
694
+ // - should internalResponse to request be blocked due to nosniff
695
+ // TODO
725
696
 
726
- // 20. If request’s integrity metadata is not the empty string, then:
727
- if (request.integrity) {
728
- // 1. Let processBodyError be this step: run fetch finale given fetchParams
729
- // and a network error.
730
- const processBodyError = (reason) =>
731
- fetchFinale(fetchParams, makeNetworkError(reason))
697
+ // 18. If response’s type is "opaque", internalResponse’s status is 206,
698
+ // internalResponse’s range-requested flag is set, and request’s header
699
+ // list does not contain `Range`, then set response and internalResponse
700
+ // to a network error.
701
+ if (
702
+ response.type === 'opaque' &&
703
+ internalResponse.status === 206 &&
704
+ internalResponse.rangeRequested &&
705
+ !request.headers.contains('range', true)
706
+ ) {
707
+ response = internalResponse = makeNetworkError()
708
+ }
732
709
 
733
- // 2. If request’s response tainting is "opaque", or response’s body is null,
734
- // then run processBodyError and abort these steps.
735
- if (request.responseTainting === 'opaque' || response.body == null) {
736
- processBodyError(response.error)
737
- return
710
+ // 19. If response is not a network error and either request’s method is
711
+ // `HEAD` or `CONNECT`, or internalResponse’s status is a null body status,
712
+ // set internalResponse’s body to null and disregard any enqueuing toward
713
+ // it (if any).
714
+ if (
715
+ response.status !== 0 &&
716
+ (request.method === 'HEAD' ||
717
+ request.method === 'CONNECT' ||
718
+ nullBodyStatus.includes(internalResponse.status))
719
+ ) {
720
+ internalResponse.body = null
721
+ fetchParams.controller.dump = true
738
722
  }
739
723
 
740
- // 3. Let processBody given bytes be these steps:
741
- const processBody = (bytes) => {
742
- // 1. If bytes do not match request’s integrity metadata,
743
- // then run processBodyError and abort these steps. [SRI]
744
- if (!bytesMatch(bytes, request.integrity)) {
745
- processBodyError('integrity mismatch')
724
+ // 20. If request’s integrity metadata is not the empty string, then:
725
+ if (request.integrity) {
726
+ // 1. Let processBodyError be this step: run fetch finale given fetchParams
727
+ // and a network error.
728
+ const processBodyError = (reason) =>
729
+ fetchFinale(fetchParams, makeNetworkError(reason))
730
+
731
+ // 2. If request’s response tainting is "opaque", or response’s body is null,
732
+ // then run processBodyError and abort these steps.
733
+ if (request.responseTainting === 'opaque' || response.body == null) {
734
+ processBodyError(response.error)
746
735
  return
747
736
  }
748
737
 
749
- // 2. Set response’s body to bytes as a body.
750
- response.body = safelyExtractBody(bytes)[0]
738
+ // 3. Let processBody given bytes be these steps:
739
+ const processBody = (bytes) => {
740
+ // 1. If bytes do not match request’s integrity metadata,
741
+ // then run processBodyError and abort these steps. [SRI]
742
+ if (!bytesMatch(bytes, request.integrity)) {
743
+ processBodyError('integrity mismatch')
744
+ return
745
+ }
746
+
747
+ // 2. Set response’s body to bytes as a body.
748
+ response.body = safelyExtractBody(bytes)[0]
749
+
750
+ // 3. Run fetch finale given fetchParams and response.
751
+ fetchFinale(fetchParams, response)
752
+ }
751
753
 
752
- // 3. Run fetch finale given fetchParams and response.
754
+ // 4. Fully read response’s body given processBody and processBodyError.
755
+ fullyReadBody(response.body, processBody, processBodyError)
756
+ } else {
757
+ // 21. Otherwise, run fetch finale given fetchParams and response.
753
758
  fetchFinale(fetchParams, response)
754
759
  }
755
-
756
- // 4. Fully read response’s body given processBody and processBodyError.
757
- await fullyReadBody(response.body, processBody, processBodyError)
758
- } else {
759
- // 21. Otherwise, run fetch finale given fetchParams and response.
760
- fetchFinale(fetchParams, response)
760
+ } catch (err) {
761
+ fetchParams.controller.terminate(err)
761
762
  }
762
763
  }
763
764
 
@@ -1909,15 +1910,11 @@ async function httpNetworkFetch (
1909
1910
  // cancelAlgorithm set to cancelAlgorithm.
1910
1911
  const stream = new ReadableStream(
1911
1912
  {
1912
- async start (controller) {
1913
+ start (controller) {
1913
1914
  fetchParams.controller.controller = controller
1914
1915
  },
1915
- async pull (controller) {
1916
- await pullAlgorithm(controller)
1917
- },
1918
- async cancel (reason) {
1919
- await cancelAlgorithm(reason)
1920
- },
1916
+ pull: pullAlgorithm,
1917
+ cancel: cancelAlgorithm,
1921
1918
  type: 'bytes'
1922
1919
  }
1923
1920
  )
@@ -2055,7 +2052,7 @@ async function httpNetworkFetch (
2055
2052
 
2056
2053
  function dispatch ({ body }) {
2057
2054
  const url = requestCurrentURL(request)
2058
- /** @type {import('../..').Agent} */
2055
+ /** @type {import('../../..').Agent} */
2059
2056
  const agent = fetchParams.controller.dispatcher
2060
2057
 
2061
2058
  return new Promise((resolve, reject) => agent.dispatch(
@@ -2104,12 +2101,11 @@ async function httpNetworkFetch (
2104
2101
 
2105
2102
  onHeaders (status, rawHeaders, resume, statusText) {
2106
2103
  if (status < 200) {
2107
- return
2104
+ return false
2108
2105
  }
2109
2106
 
2110
2107
  /** @type {string[]} */
2111
2108
  let codings = []
2112
- let location = ''
2113
2109
 
2114
2110
  const headersList = new HeadersList()
2115
2111
 
@@ -2122,7 +2118,7 @@ async function httpNetworkFetch (
2122
2118
  // "All content-coding values are case-insensitive..."
2123
2119
  codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
2124
2120
  }
2125
- location = headersList.get('location', true)
2121
+ const location = headersList.get('location', true)
2126
2122
 
2127
2123
  this.body = new Readable({ read: resume })
2128
2124
 
@@ -2155,6 +2151,12 @@ async function httpNetworkFetch (
2155
2151
  flush: zlib.constants.BROTLI_OPERATION_FLUSH,
2156
2152
  finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
2157
2153
  }))
2154
+ } else if (coding === 'zstd' && typeof zlib.createZstdDecompress === 'function') {
2155
+ // Node.js v23.8.0+ and v22.15.0+ supports Zstandard
2156
+ decoders.push(zlib.createZstdDecompress({
2157
+ flush: zlib.constants.ZSTD_e_continue,
2158
+ finishFlush: zlib.constants.ZSTD_e_end
2159
+ }))
2158
2160
  } else {
2159
2161
  decoders.length = 0
2160
2162
  break
@@ -4,7 +4,6 @@
4
4
 
5
5
  const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body')
6
6
  const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers')
7
- const { FinalizationRegistry } = require('./dispatcher-weakref')()
8
7
  const util = require('../../core/util')
9
8
  const nodeUtil = require('node:util')
10
9
  const {
@@ -23,7 +22,7 @@ const {
23
22
  requestDuplex
24
23
  } = require('./constants')
25
24
  const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
26
- const { webidl } = require('./webidl')
25
+ const { webidl } = require('../webidl')
27
26
  const { URLSerializer } = require('./data-url')
28
27
  const { kConstruct } = require('../../core/symbols')
29
28
  const assert = require('node:assert')
@@ -109,8 +108,8 @@ class Request {
109
108
  const prefix = 'Request constructor'
110
109
  webidl.argumentLengthCheck(arguments, 1, prefix)
111
110
 
112
- input = webidl.converters.RequestInfo(input, prefix, 'input')
113
- init = webidl.converters.RequestInit(init, prefix, 'init')
111
+ input = webidl.converters.RequestInfo(input)
112
+ init = webidl.converters.RequestInit(init)
114
113
 
115
114
  // 1. Let request be null.
116
115
  let request = null
@@ -937,7 +936,7 @@ function cloneRequest (request) {
937
936
  // 2. If request’s body is non-null, set newRequest’s body to the
938
937
  // result of cloning request’s body.
939
938
  if (request.body != null) {
940
- newRequest.body = cloneBody(newRequest, request.body)
939
+ newRequest.body = cloneBody(request.body)
941
940
  }
942
941
 
943
942
  // 3. Return newRequest.
@@ -993,8 +992,13 @@ Object.defineProperties(Request.prototype, {
993
992
 
994
993
  webidl.is.Request = webidl.util.MakeTypeAssertion(Request)
995
994
 
996
- // https://fetch.spec.whatwg.org/#requestinfo
997
- webidl.converters.RequestInfo = function (V, prefix, argument) {
995
+ /**
996
+ * @param {*} V
997
+ * @returns {import('../../../types/fetch').Request|string}
998
+ *
999
+ * @see https://fetch.spec.whatwg.org/#requestinfo
1000
+ */
1001
+ webidl.converters.RequestInfo = function (V) {
998
1002
  if (typeof V === 'string') {
999
1003
  return webidl.converters.USVString(V)
1000
1004
  }
@@ -1006,7 +1010,11 @@ webidl.converters.RequestInfo = function (V, prefix, argument) {
1006
1010
  return webidl.converters.USVString(V)
1007
1011
  }
1008
1012
 
1009
- // https://fetch.spec.whatwg.org/#requestinit
1013
+ /**
1014
+ * @param {*} V
1015
+ * @returns {import('../../../types/fetch').RequestInit}
1016
+ * @see https://fetch.spec.whatwg.org/#requestinit
1017
+ */
1010
1018
  webidl.converters.RequestInit = webidl.dictionaryConverter([
1011
1019
  {
1012
1020
  key: 'method',