undici 4.11.3 → 4.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.
package/README.md CHANGED
@@ -203,13 +203,13 @@ not support or does not fully implement.
203
203
  * https://fetch.spec.whatwg.org/#garbage-collection
204
204
 
205
205
  The [Fetch Standard](https://fetch.spec.whatwg.org) allows users to skip consuming the response body by relying on
206
- [garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does the same. However,
207
- garbage collection in Node is less aggressive and deterministic (due to the lack
208
- of clear idle periods that browser have through the rendering refresh rate)
209
- which means that leaving the release of connection resources to the garbage collector
210
- can lead to excessive connection usage, reduced performance (due to less connection re-use),
211
- and even stalls or deadlocks when running out of connections. Therefore, it is highly
212
- recommended to always either consume or cancel the response body.
206
+ [garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does not do the same. Therefore, it is important to always either consume or cancel the response body.
207
+
208
+ Garbage collection in Node is less aggressive and deterministic
209
+ (due to the lack of clear idle periods that browser have through the rendering refresh rate)
210
+ which means that leaving the release of connection resources to the garbage collector can lead
211
+ to excessive connection usage, reduced performance (due to less connection re-use), and even
212
+ stalls or deadlocks when running out of connections.
213
213
 
214
214
  ```js
215
215
  // Do
@@ -289,7 +289,7 @@ pipeline requests, without checking whether the connection is persistent.
289
289
  Hence, automatic fallback to HTTP/1.0 or HTTP/1.1 without pipelining is
290
290
  not supported.
291
291
 
292
- Undici will immediately pipeline when retrying requests afters a failed
292
+ Undici will immediately pipeline when retrying requests after a failed
293
293
  connection. However, Undici will not retry the first remaining requests in
294
294
  the prior pipeline and instead error the corresponding callback/promise/stream.
295
295
 
package/lib/core/util.js CHANGED
@@ -150,7 +150,7 @@ function isDestroyed (stream) {
150
150
  return !stream || !!(stream.destroyed || stream[kDestroyed])
151
151
  }
152
152
 
153
- function isAborted (stream) {
153
+ function isReadableAborted (stream) {
154
154
  const state = stream && stream._readableState
155
155
  return isDestroyed(stream) && state && !state.endEmitted
156
156
  }
@@ -244,15 +244,28 @@ function validateHandler (handler, method, upgrade) {
244
244
  // A body is disturbed if it has been read from and it cannot
245
245
  // be re-used without losing state or data.
246
246
  function isDisturbed (body) {
247
- const state = body && body._readableState
248
247
  return !!(body && (
249
- (stream.isDisturbed && stream.isDisturbed(body)) ||
250
- body[kBodyUsed] ||
251
- body.readableDidRead || (state && state.dataEmitted) ||
252
- isAborted(body)
248
+ stream.isDisturbed
249
+ ? stream.isDisturbed(body) || body[kBodyUsed] // TODO (fix): Why is body[kBodyUsed] needed?
250
+ : body[kBodyUsed] ||
251
+ body.readableDidRead ||
252
+ (body._readableState && body._readableState.dataEmitted) ||
253
+ isReadableAborted(body)
253
254
  ))
254
255
  }
255
256
 
257
+ function isErrored (body) {
258
+ return !!(body && (
259
+ stream.isErrored
260
+ ? stream.isErrored(body)
261
+ : /state: 'errored'/.test(nodeUtil.inspect(body)
262
+ )))
263
+ }
264
+
265
+ function isReadable (body) {
266
+ return !!(body && /state: 'readable'/.test(nodeUtil.inspect(body)))
267
+ }
268
+
256
269
  function getSocketInfo (socket) {
257
270
  return {
258
271
  localAddress: socket.localAddress,
@@ -310,8 +323,10 @@ module.exports = {
310
323
  kEnumerableProperty,
311
324
  nop,
312
325
  isDisturbed,
326
+ isErrored,
327
+ isReadable,
313
328
  toUSVString: nodeUtil.toUSVString || ((val) => `${val}`),
314
- isAborted,
329
+ isReadableAborted,
315
330
  isBlobLike,
316
331
  parseOrigin,
317
332
  parseURL,
package/lib/fetch/body.js CHANGED
@@ -3,12 +3,13 @@
3
3
  const util = require('../core/util')
4
4
  const { ReadableStreamFrom, toUSVString, isBlobLike } = require('./util')
5
5
  const { FormData } = require('./formdata')
6
- const { kState, kError } = require('./symbols')
6
+ const { kState } = require('./symbols')
7
7
  const { Blob } = require('buffer')
8
8
  const { kBodyUsed } = require('../core/symbols')
9
9
  const assert = require('assert')
10
10
  const nodeUtil = require('util')
11
11
  const { NotSupportedError } = require('../core/errors')
12
+ const { isErrored } = require('../core/util')
12
13
 
13
14
  let ReadableStream
14
15
 
@@ -187,7 +188,7 @@ function extractBody (object, keepalive = false) {
187
188
  // Whenever one or more bytes are available and stream is not errored,
188
189
  // enqueue a Uint8Array wrapping an ArrayBuffer containing the available
189
190
  // bytes into stream.
190
- if (!/state: 'errored'/.test(nodeUtil.inspect(stream))) {
191
+ if (!isErrored(stream)) {
191
192
  controller.enqueue(new Uint8Array(value))
192
193
  }
193
194
  }
@@ -268,10 +269,6 @@ const methods = {
268
269
  if (this[kState].body) {
269
270
  const stream = this[kState].body.stream
270
271
 
271
- if (stream[kError]) {
272
- throw stream[kError]
273
- }
274
-
275
272
  if (util.isDisturbed(stream)) {
276
273
  throw new TypeError('disturbed')
277
274
  }
@@ -352,19 +349,12 @@ const properties = {
352
349
  }
353
350
  }
354
351
 
355
- function cancelBody (body, reason) {
356
- if (body.stream && !/state: 'errored'/.test(nodeUtil.inspect(body.stream))) {
357
- body.stream.cancel(reason)
358
- }
359
- }
360
-
361
352
  function mixinBody (prototype) {
362
353
  Object.assign(prototype, methods)
363
354
  Object.defineProperties(prototype, properties)
364
355
  }
365
356
 
366
357
  module.exports = {
367
- cancelBody,
368
358
  extractBody,
369
359
  safelyExtractBody,
370
360
  cloneBody,
@@ -24,17 +24,17 @@ const {
24
24
  requestCurrentURL,
25
25
  setRequestReferrerPolicyOnRedirect,
26
26
  tryUpgradeRequestToAPotentiallyTrustworthyURL,
27
- makeTimingInfo,
27
+ createOpaqueTimingInfo,
28
28
  appendFetchMetadata,
29
29
  corsCheck,
30
30
  crossOriginResourcePolicyCheck,
31
31
  determineRequestsReferrer,
32
32
  coarsenedSharedCurrentTime
33
33
  } = require('./util')
34
- const { kState, kHeaders, kGuard, kRealm, kError } = require('./symbols')
34
+ const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
35
35
  const { AbortError } = require('../core/errors')
36
36
  const assert = require('assert')
37
- const { safelyExtractBody, cancelBody } = require('./body')
37
+ const { safelyExtractBody } = require('./body')
38
38
  const {
39
39
  redirectStatus,
40
40
  nullBodyStatus,
@@ -44,14 +44,32 @@ const {
44
44
  } = require('./constants')
45
45
  const { kHeadersList } = require('../core/symbols')
46
46
  const EE = require('events')
47
- const { PassThrough, pipeline, compose } = require('stream')
47
+ const { PassThrough, pipeline } = require('stream')
48
+ const { isErrored, isReadable } = require('../core/util')
48
49
 
49
50
  let ReadableStream
50
51
 
51
- // https://fetch.spec.whatwg.org/#garbage-collection
52
- const registry = new FinalizationRegistry((abort) => {
53
- abort()
54
- })
52
+ class Fetch extends EE {
53
+ constructor (dispatcher) {
54
+ super()
55
+
56
+ this.dispatcher = dispatcher
57
+ this.terminated = null
58
+ this.connection = null
59
+ this.dump = false
60
+ }
61
+
62
+ terminate ({ reason, aborted } = {}) {
63
+ if (this.terminated) {
64
+ return
65
+ }
66
+ this.terminated = { aborted, reason }
67
+
68
+ this.connection?.destroy(reason)
69
+
70
+ this.emit('terminated', reason)
71
+ }
72
+ }
55
73
 
56
74
  // https://fetch.spec.whatwg.org/#fetch-method
57
75
  async function fetch (...args) {
@@ -73,25 +91,7 @@ async function fetch (...args) {
73
91
  const resource = args[0]
74
92
  const init = args.length >= 1 ? args[1] ?? {} : {}
75
93
 
76
- const context = Object.assign(new EE(), {
77
- dispatcher: this,
78
- terminated: false,
79
- connection: null,
80
- dump: false,
81
- terminate ({ reason, aborted } = {}) {
82
- if (this.terminated) {
83
- return
84
- }
85
- this.terminated = { aborted }
86
-
87
- if (this.connection) {
88
- this.connection.destroy(reason)
89
- this.connection = null
90
- }
91
-
92
- this.emit('terminated', reason)
93
- }
94
- })
94
+ const context = new Fetch(this)
95
95
 
96
96
  // 1. Let p be a new promise.
97
97
  const p = createDeferredPromise()
@@ -153,7 +153,7 @@ async function fetch (...args) {
153
153
  const handleFetchDone = (response) =>
154
154
  finalizeAndReportTiming(response, 'fetch')
155
155
 
156
- // 12. Fetch request with processResponseDone set to handleFetchDone,
156
+ // 12. Fetch request with processResponseEndOfBody set to handleFetchDone,
157
157
  // and processResponse given response being these substeps:
158
158
  const processResponse = (response) => {
159
159
  // 1. If locallyAborted is true, terminate these substeps.
@@ -193,7 +193,7 @@ async function fetch (...args) {
193
193
  fetching
194
194
  .call(context, {
195
195
  request,
196
- processResponseDone: handleFetchDone,
196
+ processResponseEndOfBody: handleFetchDone,
197
197
  processResponse
198
198
  })
199
199
  .catch((err) => {
@@ -226,11 +226,9 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
226
226
 
227
227
  // 6. If response’s timing allow passed flag is not set, then:
228
228
  if (!timingInfo.timingAllowPassed) {
229
- // 1. Set timingInfo to a new fetch timing info whose start time and
230
- // post-redirect start time are timingInfo’s start time.
231
- timingInfo = makeTimingInfo({
232
- startTime: timingInfo.startTime,
233
- postRedirectStartTime: timingInfo.postRedirectStartTime
229
+ // 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo.
230
+ timingInfo = createOpaqueTimingInfo({
231
+ startTime: timingInfo.startTime
234
232
  })
235
233
 
236
234
  // 2. Set cacheState to the empty string.
@@ -273,8 +271,14 @@ function abortFetch (p, request, responseObject) {
273
271
 
274
272
  // 3. If request’s body is not null and is readable, then cancel request’s
275
273
  // body with error.
276
- if (request.body != null) {
277
- cancelBody(request.body, error)
274
+ if (request.body != null && isReadable(request.body?.stream)) {
275
+ request.body.stream.cancel(error).catch((err) => {
276
+ if (err.code === 'ERR_INVALID_STATE') {
277
+ // Node bug?
278
+ return
279
+ }
280
+ throw err
281
+ })
278
282
  }
279
283
 
280
284
  // 4. If responseObject is null, then return.
@@ -287,13 +291,27 @@ function abortFetch (p, request, responseObject) {
287
291
 
288
292
  // 6. If response’s body is not null and is readable, then error response’s
289
293
  // body with error.
290
- if (response.body != null) {
291
- cancelBody(response.body, error)
294
+ if (response.body != null && isReadable(response.body?.stream)) {
295
+ response.body.stream.cancel(error).catch((err) => {
296
+ if (err.code === 'ERR_INVALID_STATE') {
297
+ // Node bug?
298
+ return
299
+ }
300
+ throw err
301
+ })
292
302
  }
293
303
  }
294
304
 
295
305
  // https://fetch.spec.whatwg.org/#fetching
296
- function fetching ({ request, processResponse, processResponseDone }) {
306
+ function fetching ({
307
+ request,
308
+ processRequestBodyChunkLength,
309
+ processRequestEndOfBody,
310
+ processResponse,
311
+ processResponseEndOfBody,
312
+ processResponseConsumeBody,
313
+ useParallelQueue = false,
314
+ }) {
297
315
  // 1. Let taskDestination be null.
298
316
  let taskDestination = null
299
317
 
@@ -319,26 +337,28 @@ function fetching ({ request, processResponse, processResponseDone }) {
319
337
  // post-redirect start time are the coarsened shared current time given
320
338
  // crossOriginIsolatedCapability.
321
339
  const currenTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability)
322
- const timingInfo = makeTimingInfo({
323
- startTime: currenTime,
324
- postRedirectStartTime: currenTime
340
+ const timingInfo = createOpaqueTimingInfo({
341
+ startTime: currenTime
325
342
  })
326
343
 
327
- // 6. Let fetchParams be a new fetch params whose request is request, timing
328
- // info is timingInfo, process request body is processRequestBody,
329
- // process request end-of-body is processRequestEndOfBody, process response
330
- // is processResponse, process response end-of-body is
331
- // processResponseEndOfBody, process response done is processResponseDone,
332
- // task destination is taskDestination, and cross-origin isolated capability
333
- // is crossOriginIsolatedCapability.
344
+ // 6. Let fetchParams be a new fetch params whose
345
+ // request is request,
346
+ // timing info is timingInfo,
347
+ // process request body chunk length is processRequestBodyChunkLength,
348
+ // process request end-of-body is processRequestEndOfBody,
349
+ // process response is processResponse,
350
+ // process response consume body is processResponseConsumeBody,
351
+ // process response end-of-body is processResponseEndOfBody,
352
+ // task destination is taskDestination,
353
+ // and cross-origin isolated capability is crossOriginIsolatedCapability.
334
354
  const fetchParams = {
335
355
  request,
336
356
  timingInfo,
337
- processRequestBody: null,
338
- processRequestEndOfBody: null,
357
+ processRequestBodyChunkLength,
358
+ processRequestEndOfBody,
339
359
  processResponse,
340
- processResponseEndOfBody: null,
341
- processResponseDone,
360
+ processResponseConsumeBody,
361
+ processResponseEndOfBody,
342
362
  taskDestination,
343
363
  crossOriginIsolatedCapability
344
364
  }
@@ -656,10 +676,9 @@ async function mainFetch (fetchParams, recursive = false) {
656
676
  const processBodyError = (reason) =>
657
677
  fetchFinale(fetchParams, makeNetworkError(reason))
658
678
 
659
- // 2. If request’s response tainting is "opaque", response is a network
660
- // error, or response’s body is null, then run processBodyError and abort
661
- // these steps.
662
- if (request.responseTainting === 'opaque' && response.status === 0) {
679
+ // 2. If request’s response tainting is "opaque", or response’s body is null,
680
+ // then run processBodyError and abort these steps.
681
+ if (request.responseTainting === 'opaque' || response.body == null) {
663
682
  processBodyError(response.error)
664
683
  return
665
684
  }
@@ -717,13 +736,13 @@ function fetchFinale (fetchParams, response) {
717
736
  fetchParams.processResponse(response)
718
737
  }
719
738
 
720
- // 2. If fetchParams’s process response end-of-body is non-null, then:.
739
+ // 2. If fetchParams’s process response consume is non-null, then:.
721
740
  // TODO
722
741
  // 1. Let processBody given nullOrBytes be this step: run fetchParams’s
723
- // process response end-of-body given response and nullOrBytes.on.
742
+ // process response consume given response and nullOrBytes.on.
724
743
  // TODO
725
744
  // 2. Let processBodyError be this step: run fetchParams’s process
726
- // response end-of-body given response and failure.on.
745
+ // response consume given response and failure.on.
727
746
  // TODO
728
747
  // 3. If response’s body is null, then queue a fetch task to run
729
748
  // processBody given null, with fetchParams’s task destination.on.
@@ -1256,11 +1275,15 @@ async function httpNetworkOrCacheFetch (
1256
1275
  // 3. If the ongoing fetch is terminated, then:
1257
1276
  if (context.terminated) {
1258
1277
  // 1. Let aborted be the termination’s aborted flag.
1278
+ const aborted = context.terminated.aborted
1279
+
1259
1280
  // 2. If aborted is set, then return an aborted network error.
1281
+ if (aborted) {
1282
+ return makeNetworkError(new AbortError())
1283
+ }
1284
+
1260
1285
  // 3. Return a network error.
1261
- return makeNetworkError(
1262
- context.terminated.aborted ? new AbortError() : null
1263
- )
1286
+ return makeNetworkError(context.terminated.reason)
1264
1287
  }
1265
1288
 
1266
1289
  // 4. Prompt the end user as appropriate in request’s window and store
@@ -1290,10 +1313,12 @@ async function httpNetworkOrCacheFetch (
1290
1313
  const aborted = context.terminated.aborted
1291
1314
 
1292
1315
  // 2. If aborted is set, then return an aborted network error.
1293
- const reason = aborted ? new AbortError() : new Error('terminated')
1316
+ if (aborted) {
1317
+ return makeNetworkError(new AbortError())
1318
+ }
1294
1319
 
1295
1320
  // 3. Return a network error.
1296
- return makeNetworkError(reason)
1321
+ return makeNetworkError(context.terminated.reason)
1297
1322
  }
1298
1323
 
1299
1324
  // 2. Set response to the result of running HTTP-network-or-cache
@@ -1480,13 +1505,15 @@ function httpNetworkFetch (
1480
1505
  const aborted = this.terminated.aborted
1481
1506
 
1482
1507
  // 2. If connection uses HTTP/2, then transmit an RST_STREAM frame.
1483
- this.connection?.destroy()
1508
+ this.connection.destroy()
1484
1509
 
1485
1510
  // 3. If aborted is set, then return an aborted network error.
1486
- const reason = aborted ? new AbortError() : new Error('terminated')
1511
+ if (aborted) {
1512
+ return resolve(makeNetworkError(new AbortError()))
1513
+ }
1487
1514
 
1488
1515
  // 4. Return a network error.
1489
- resolve(makeNetworkError(reason))
1516
+ return resolve(makeNetworkError(this.terminated.reason))
1490
1517
  }
1491
1518
 
1492
1519
  // 10. Let pullAlgorithm be an action that resumes the ongoing fetch
@@ -1531,7 +1558,6 @@ function httpNetworkFetch (
1531
1558
  await pullAlgorithm(controller)
1532
1559
  },
1533
1560
  async cancel (reason) {
1534
- stream[kError] = reason
1535
1561
  await cancelAlgorithm(reason)
1536
1562
  }
1537
1563
  },
@@ -1570,29 +1596,19 @@ function httpNetworkFetch (
1570
1596
  response.aborted = true
1571
1597
 
1572
1598
  // 2. If stream is readable, error stream with an "AbortError" DOMException.
1573
- try {
1599
+ if (isReadable(stream)) {
1574
1600
  this.controller.error(new AbortError())
1575
- } catch (err) {
1576
- // Will throw TypeError if body is not readable.
1577
- if (err.name !== 'TypeError') {
1578
- throw err
1579
- }
1580
1601
  }
1581
1602
  } else {
1582
1603
  // 4. Otherwise, if stream is readable, error stream with a TypeError.
1583
- try {
1604
+ if (isReadable(stream)) {
1584
1605
  this.controller.error(new TypeError('terminated'))
1585
- } catch (err) {
1586
- // Will throw TypeError if body is not readable.
1587
- if (err.name !== 'TypeError') {
1588
- throw err
1589
- }
1590
1606
  }
1591
1607
  }
1592
1608
 
1593
1609
  // 5. If connection uses HTTP/2, then transmit an RST_STREAM frame.
1594
1610
  // 6. Otherwise, the user agent should close connection unless it would be bad for performance to do so.
1595
- this.connection?.destroy()
1611
+ this.connection.destroy()
1596
1612
  }
1597
1613
 
1598
1614
  // 19. Return response.
@@ -1638,8 +1654,6 @@ function httpNetworkFetch (
1638
1654
  )
1639
1655
  }
1640
1656
 
1641
- registry.register(stream, this.abort, this)
1642
-
1643
1657
  response = makeResponse({
1644
1658
  status,
1645
1659
  statusText,
@@ -1672,29 +1686,16 @@ function httpNetworkFetch (
1672
1686
  }
1673
1687
  }
1674
1688
 
1675
- let iterator
1676
-
1677
1689
  if (decoders.length > 1) {
1678
- if (compose) {
1679
- this.decoder = compose(...decoders)
1680
- iterator = this.decoder[Symbol.asyncIterator]()
1681
- } else {
1682
- this.decoder = new PassThrough()
1683
- iterator = pipeline(this.decoder, ...decoders, () => {})[
1684
- Symbol.asyncIterator
1685
- ]()
1686
- }
1687
- } else if (decoders.length === 1) {
1688
- this.decoder = decoders[0]
1689
- iterator = this.decoder[Symbol.asyncIterator]()
1690
- } else {
1691
- this.decoder = new PassThrough()
1692
- iterator = this.decoder[Symbol.asyncIterator]()
1690
+ pipeline(...decoders, () => {})
1691
+ } else if (decoders.length === 0) {
1692
+ // TODO (perf): Avoid intermediate.
1693
+ decoders.push(new PassThrough())
1693
1694
  }
1694
1695
 
1695
- if (this.decoder) {
1696
- this.decoder.on('drain', resume)
1697
- }
1696
+ this.decoder = decoders[0].on('drain', resume)
1697
+
1698
+ const iterator = decoders[decoders.length - 1][Symbol.asyncIterator]()
1698
1699
 
1699
1700
  pullAlgorithm = async (controller) => {
1700
1701
  // 4. Set bytes to the result of handling content codings given
@@ -1738,8 +1739,8 @@ function httpNetworkFetch (
1738
1739
  controller.enqueue(new Uint8Array(bytes))
1739
1740
 
1740
1741
  // 8. If stream is errored, then terminate the ongoing fetch.
1741
- if (stream[kError]) {
1742
- this.context.terminate({ reason: stream[kError] })
1742
+ if (isErrored(stream)) {
1743
+ this.context.terminate()
1743
1744
  return
1744
1745
  }
1745
1746
 
@@ -1782,14 +1783,10 @@ function httpNetworkFetch (
1782
1783
  },
1783
1784
 
1784
1785
  onComplete () {
1785
- registry.unregister(this)
1786
-
1787
1786
  this.decoder.end()
1788
1787
  },
1789
1788
 
1790
1789
  onError (error) {
1791
- registry.unregister(this)
1792
-
1793
1790
  this.decoder?.destroy(error)
1794
1791
 
1795
1792
  this.context.terminate({ reason: error })
@@ -28,6 +28,10 @@ let TransformStream
28
28
 
29
29
  const kInit = Symbol('init')
30
30
 
31
+ const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => {
32
+ signal.removeEventListener('abort', abort)
33
+ })
34
+
31
35
  // https://fetch.spec.whatwg.org/#request-class
32
36
  class Request {
33
37
  // https://fetch.spec.whatwg.org/#dom-request
@@ -134,8 +138,48 @@ class Request {
134
138
 
135
139
  // 12. Set request to a new request with the following properties:
136
140
  request = makeRequest({
137
- ...request,
138
- window
141
+ // URL request’s URL.
142
+ // undici implementation note: this is set as the first item in request's urlList in makeRequest
143
+ // method request’s method.
144
+ method: request.method,
145
+ // header list A copy of request’s header list.
146
+ // undici implementation note: headersList is cloned in makeRequest
147
+ headersList: request.headersList,
148
+ // unsafe-request flag Set.
149
+ unsafeRequest: request.unsafeRequest,
150
+ // client This’s relevant settings object.
151
+ client: request.client,
152
+ // window window.
153
+ window,
154
+ // priority request’s priority.
155
+ priority: request.priority,
156
+ // origin request’s origin. The propagation of the origin is only significant for navigation requests
157
+ // being handled by a service worker. In this scenario a request can have an origin that is different
158
+ // from the current client.
159
+ origin: request.origin,
160
+ // referrer request’s referrer.
161
+ referrer: request.referrer,
162
+ // referrer policy request’s referrer policy.
163
+ referrerPolicy: request.referrerPolicy,
164
+ // mode request’s mode.
165
+ mode: request.mode,
166
+ // credentials mode request’s credentials mode.
167
+ credentials: request.credentials,
168
+ // cache mode request’s cache mode.
169
+ cache: request.cache,
170
+ // redirect mode request’s redirect mode.
171
+ redirect: request.redirect,
172
+ // integrity metadata request’s integrity metadata.
173
+ integrity: request.integrity,
174
+ // keepalive request’s keepalive.
175
+ keepalive: request.keepalive,
176
+ // reload-navigation flag request’s reload-navigation flag.
177
+ reloadNavigation: request.reloadNavigation,
178
+ // history-navigation flag request’s history-navigation flag.
179
+ historyNavigation: request.historyNavigation,
180
+ // URL list A clone of request’s URL list.
181
+ // undici implementation note: urlList is cloned in makeRequest
182
+ urlList: request.urlList
139
183
  })
140
184
 
141
185
  // 13. If init is not empty, then:
@@ -151,11 +195,20 @@ class Request {
151
195
  // 3. Unset request’s history-navigation flag.
152
196
  request.historyNavigation = false
153
197
 
154
- // 4. Set request’s referrer to "client"
198
+ // 4. Set request’s origin to "client".
199
+ request.origin = 'client'
200
+
201
+ // 5. Set request’s referrer to "client"
155
202
  request.referrer = 'client'
156
203
 
157
- // 5. Set request’s referrer policy to the empty string.
204
+ // 6. Set request’s referrer policy to the empty string.
158
205
  request.referrerPolicy = ''
206
+
207
+ // 7. Set request’s URL to request’s current URL.
208
+ request.url = request.urlList[request.urlList.length - 1]
209
+
210
+ // 8. Set request’s URL list to « request’s URL ».
211
+ request.urlList = [request.url]
159
212
  }
160
213
 
161
214
  // 14. If init["referrer"] exists, then:
@@ -164,7 +217,7 @@ class Request {
164
217
  const referrer = init.referrer
165
218
 
166
219
  // 2. If referrer is the empty string, then set request’s referrer to "no-referrer".
167
- if (!referrer === '') {
220
+ if (referrer === '') {
168
221
  request.referrer = 'no-referrer'
169
222
  } else {
170
223
  // 1. Let parsedReferrer be the result of parsing referrer with
@@ -328,14 +381,9 @@ class Request {
328
381
  if (signal.aborted) {
329
382
  ac.abort()
330
383
  } else {
331
- // TODO: Remove this listener on failure/success.
332
- signal.addEventListener(
333
- 'abort',
334
- function () {
335
- ac.abort()
336
- },
337
- { once: true }
338
- )
384
+ const abort = () => ac.abort()
385
+ signal.addEventListener('abort', abort, { once: true })
386
+ requestFinalizer.register(this, { signal, abort })
339
387
  }
340
388
  }
341
389
 
@@ -6,6 +6,5 @@ module.exports = {
6
6
  kSignal: Symbol('signal'),
7
7
  kState: Symbol('state'),
8
8
  kGuard: Symbol('guard'),
9
- kRealm: Symbol('realm'),
10
- kError: Symbol('error')
9
+ kRealm: Symbol('realm')
11
10
  }
package/lib/fetch/util.js CHANGED
@@ -253,20 +253,20 @@ function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) {
253
253
  return performance.now()
254
254
  }
255
255
 
256
- function makeTimingInfo (init) {
256
+ // https://fetch.spec.whatwg.org/#create-an-opaque-timing-info
257
+ function createOpaqueTimingInfo (timingInfo) {
257
258
  return {
258
- startTime: 0,
259
+ startTime: timingInfo.startTime ?? 0,
259
260
  redirectStartTime: 0,
260
261
  redirectEndTime: 0,
261
- postRedirectStartTime: 0,
262
+ postRedirectStartTime: timingInfo.startTime ?? 0,
262
263
  finalServiceWorkerStartTime: 0,
263
264
  finalNetworkResponseStartTime: 0,
264
265
  finalNetworkRequestStartTime: 0,
265
266
  endTime: 0,
266
267
  encodedBodySize: 0,
267
268
  decodedBodySize: 0,
268
- finalConnectionTimingInfo: null,
269
- ...init
269
+ finalConnectionTimingInfo: null
270
270
  }
271
271
  }
272
272
 
@@ -318,7 +318,7 @@ module.exports = {
318
318
  TAOCheck,
319
319
  corsCheck,
320
320
  crossOriginResourcePolicyCheck,
321
- makeTimingInfo,
321
+ createOpaqueTimingInfo,
322
322
  setRequestReferrerPolicyOnRedirect,
323
323
  isValidHTTPToken,
324
324
  requestBadPort,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "4.11.3",
3
+ "version": "4.12.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {