undici 6.20.0 → 6.21.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
@@ -84,6 +84,7 @@ The `body` mixins are the most common way to format the request/response body. M
84
84
 
85
85
  - [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
86
86
  - [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
87
+ - [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
87
88
  - [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
88
89
  - [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
89
90
 
@@ -488,11 +488,13 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
488
488
 
489
489
  `body` contains the following additional [body mixin](https://fetch.spec.whatwg.org/#body-mixin) methods and properties:
490
490
 
491
- - `text()`
492
- - `json()`
493
- - `arrayBuffer()`
494
- - `body`
495
- - `bodyUsed`
491
+ * [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
492
+ * [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
493
+ * [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
494
+ * [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
495
+ * [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
496
+ * `body`
497
+ * `bodyUsed`
496
498
 
497
499
  `body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`.
498
500
 
@@ -28,6 +28,7 @@ This API is implemented as per the standard, you can find documentation on [MDN]
28
28
 
29
29
  - [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
30
30
  - [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
31
+ - [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
31
32
  - [`.formData()`](https://fetch.spec.whatwg.org/#dom-body-formdata)
32
33
  - [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
33
34
  - [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
@@ -121,6 +121,11 @@ class BodyReadable extends Readable {
121
121
  return consume(this, 'blob')
122
122
  }
123
123
 
124
+ // https://fetch.spec.whatwg.org/#dom-body-bytes
125
+ async bytes () {
126
+ return consume(this, 'bytes')
127
+ }
128
+
124
129
  // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
125
130
  async arrayBuffer () {
126
131
  return consume(this, 'arrayBuffer')
@@ -306,6 +311,31 @@ function chunksDecode (chunks, length) {
306
311
  return buffer.utf8Slice(start, bufferLength)
307
312
  }
308
313
 
314
+ /**
315
+ * @param {Buffer[]} chunks
316
+ * @param {number} length
317
+ * @returns {Uint8Array}
318
+ */
319
+ function chunksConcat (chunks, length) {
320
+ if (chunks.length === 0 || length === 0) {
321
+ return new Uint8Array(0)
322
+ }
323
+ if (chunks.length === 1) {
324
+ // fast-path
325
+ return new Uint8Array(chunks[0])
326
+ }
327
+ const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer)
328
+
329
+ let offset = 0
330
+ for (let i = 0; i < chunks.length; ++i) {
331
+ const chunk = chunks[i]
332
+ buffer.set(chunk, offset)
333
+ offset += chunk.length
334
+ }
335
+
336
+ return buffer
337
+ }
338
+
309
339
  function consumeEnd (consume) {
310
340
  const { type, body, resolve, stream, length } = consume
311
341
 
@@ -315,17 +345,11 @@ function consumeEnd (consume) {
315
345
  } else if (type === 'json') {
316
346
  resolve(JSON.parse(chunksDecode(body, length)))
317
347
  } else if (type === 'arrayBuffer') {
318
- const dst = new Uint8Array(length)
319
-
320
- let pos = 0
321
- for (const buf of body) {
322
- dst.set(buf, pos)
323
- pos += buf.byteLength
324
- }
325
-
326
- resolve(dst.buffer)
348
+ resolve(chunksConcat(body, length).buffer)
327
349
  } else if (type === 'blob') {
328
350
  resolve(new Blob(body, { type: stream[kContentType] }))
351
+ } else if (type === 'bytes') {
352
+ resolve(chunksConcat(body, length))
329
353
  }
330
354
 
331
355
  consumeFinish(consume)
@@ -220,6 +220,11 @@ const setupConnectTimeout = process.platform === 'win32'
220
220
  * @param {number} opts.port
221
221
  */
222
222
  function onConnectTimeout (socket, opts) {
223
+ // The socket could be already garbage collected
224
+ if (socket == null) {
225
+ return
226
+ }
227
+
223
228
  let message = 'Connect Timeout Error'
224
229
  if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
225
230
  message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},`
@@ -860,7 +860,10 @@ function writeH1 (client, request) {
860
860
  const expectsPayload = (
861
861
  method === 'PUT' ||
862
862
  method === 'POST' ||
863
- method === 'PATCH'
863
+ method === 'PATCH' ||
864
+ method === 'QUERY' ||
865
+ method === 'PROPFIND' ||
866
+ method === 'PROPPATCH'
864
867
  )
865
868
 
866
869
  if (util.isFormDataLike(body)) {
@@ -1139,7 +1142,7 @@ function writeBuffer (abort, body, client, request, socket, contentLength, heade
1139
1142
  socket.uncork()
1140
1143
  request.onBodySent(body)
1141
1144
 
1142
- if (!expectsPayload) {
1145
+ if (!expectsPayload && request.reset !== false) {
1143
1146
  socket[kReset] = true
1144
1147
  }
1145
1148
  }
@@ -1169,7 +1172,7 @@ async function writeBlob (abort, body, client, request, socket, contentLength, h
1169
1172
  request.onBodySent(buffer)
1170
1173
  request.onRequestSent()
1171
1174
 
1172
- if (!expectsPayload) {
1175
+ if (!expectsPayload && request.reset !== false) {
1173
1176
  socket[kReset] = true
1174
1177
  }
1175
1178
 
@@ -1270,7 +1273,7 @@ class AsyncWriter {
1270
1273
  socket.cork()
1271
1274
 
1272
1275
  if (bytesWritten === 0) {
1273
- if (!expectsPayload) {
1276
+ if (!expectsPayload && request.reset !== false) {
1274
1277
  socket[kReset] = true
1275
1278
  }
1276
1279
 
@@ -24,7 +24,9 @@ const {
24
24
  kOnError,
25
25
  kMaxConcurrentStreams,
26
26
  kHTTP2Session,
27
- kResume
27
+ kResume,
28
+ kSize,
29
+ kHTTPContext
28
30
  } = require('../core/symbols.js')
29
31
 
30
32
  const kOpenStreams = Symbol('open streams')
@@ -160,11 +162,10 @@ async function connectH2 (client, socket) {
160
162
  version: 'h2',
161
163
  defaultPipelining: Infinity,
162
164
  write (...args) {
163
- // TODO (fix): return
164
- writeH2(client, ...args)
165
+ return writeH2(client, ...args)
165
166
  },
166
167
  resume () {
167
-
168
+ resumeH2(client)
168
169
  },
169
170
  destroy (err, callback) {
170
171
  if (closed) {
@@ -183,6 +184,20 @@ async function connectH2 (client, socket) {
183
184
  }
184
185
  }
185
186
 
187
+ function resumeH2 (client) {
188
+ const socket = client[kSocket]
189
+
190
+ if (socket?.destroyed === false) {
191
+ if (client[kSize] === 0 && client[kMaxConcurrentStreams] === 0) {
192
+ socket.unref()
193
+ client[kHTTP2Session].unref()
194
+ } else {
195
+ socket.ref()
196
+ client[kHTTP2Session].ref()
197
+ }
198
+ }
199
+ }
200
+
186
201
  function onHttp2SessionError (err) {
187
202
  assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
188
203
 
@@ -210,17 +225,32 @@ function onHttp2SessionEnd () {
210
225
  * along with the socket right away
211
226
  */
212
227
  function onHTTP2GoAway (code) {
213
- const err = new RequestAbortedError(`HTTP/2: "GOAWAY" frame received with code ${code}`)
228
+ // We cannot recover, so best to close the session and the socket
229
+ const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${code}`, util.getSocketInfo(this))
230
+ const client = this[kClient]
214
231
 
215
- // We need to trigger the close cycle right away
216
- // We need to destroy the session and the socket
217
- // Requests should be failed with the error after the current one is handled
218
- this[kSocket][kError] = err
219
- this[kClient][kOnError](err)
232
+ client[kSocket] = null
233
+ client[kHTTPContext] = null
220
234
 
221
- this.unref()
235
+ if (this[kHTTP2Session] != null) {
236
+ this[kHTTP2Session].destroy(err)
237
+ this[kHTTP2Session] = null
238
+ }
222
239
 
223
240
  util.destroy(this[kSocket], err)
241
+
242
+ // Fail head of pipeline.
243
+ const request = client[kQueue][client[kRunningIdx]]
244
+ client[kQueue][client[kRunningIdx]++] = null
245
+ util.errorRequest(client, request, err)
246
+
247
+ client[kPendingIdx] = client[kRunningIdx]
248
+
249
+ assert(client[kRunning] === 0)
250
+
251
+ client.emit('disconnect', client[kUrl], [client], err)
252
+
253
+ client[kResume]()
224
254
  }
225
255
 
226
256
  // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
@@ -237,10 +267,6 @@ function writeH2 (client, request) {
237
267
  return false
238
268
  }
239
269
 
240
- if (request.aborted) {
241
- return false
242
- }
243
-
244
270
  const headers = {}
245
271
  for (let n = 0; n < reqHeaders.length; n += 2) {
246
272
  const key = reqHeaders[n + 0]
@@ -283,6 +309,8 @@ function writeH2 (client, request) {
283
309
  // We do not destroy the socket as we can continue using the session
284
310
  // the stream get's destroyed and the session remains to create new streams
285
311
  util.destroy(body, err)
312
+ client[kQueue][client[kRunningIdx]++] = null
313
+ client[kResume]()
286
314
  }
287
315
 
288
316
  try {
@@ -293,6 +321,10 @@ function writeH2 (client, request) {
293
321
  util.errorRequest(client, request, err)
294
322
  }
295
323
 
324
+ if (request.aborted) {
325
+ return false
326
+ }
327
+
296
328
  if (method === 'CONNECT') {
297
329
  session.ref()
298
330
  // We are already connected, streams are pending, first request
@@ -304,10 +336,12 @@ function writeH2 (client, request) {
304
336
  if (stream.id && !stream.pending) {
305
337
  request.onUpgrade(null, null, stream)
306
338
  ++session[kOpenStreams]
339
+ client[kQueue][client[kRunningIdx]++] = null
307
340
  } else {
308
341
  stream.once('ready', () => {
309
342
  request.onUpgrade(null, null, stream)
310
343
  ++session[kOpenStreams]
344
+ client[kQueue][client[kRunningIdx]++] = null
311
345
  })
312
346
  }
313
347
 
@@ -428,17 +462,20 @@ function writeH2 (client, request) {
428
462
  // Present specially when using pipeline or stream
429
463
  if (stream.state?.state == null || stream.state.state < 6) {
430
464
  request.onComplete([])
431
- return
432
465
  }
433
466
 
434
- // Stream is closed or half-closed-remote (6), decrement counter and cleanup
435
- // It does not have sense to continue working with the stream as we do not
436
- // have yet RST_STREAM support on client-side
437
467
  if (session[kOpenStreams] === 0) {
468
+ // Stream is closed or half-closed-remote (6), decrement counter and cleanup
469
+ // It does not have sense to continue working with the stream as we do not
470
+ // have yet RST_STREAM support on client-side
471
+
438
472
  session.unref()
439
473
  }
440
474
 
441
475
  abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
476
+ client[kQueue][client[kRunningIdx]++] = null
477
+ client[kPendingIdx] = client[kRunningIdx]
478
+ client[kResume]()
442
479
  })
443
480
 
444
481
  stream.once('close', () => {
@@ -63,6 +63,8 @@ let deprecatedInterceptorWarned = false
63
63
 
64
64
  const kClosedResolve = Symbol('kClosedResolve')
65
65
 
66
+ const noop = () => {}
67
+
66
68
  function getPipelining (client) {
67
69
  return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1
68
70
  }
@@ -442,7 +444,7 @@ async function connect (client) {
442
444
  })
443
445
 
444
446
  if (client.destroyed) {
445
- util.destroy(socket.on('error', () => {}), new ClientDestroyedError())
447
+ util.destroy(socket.on('error', noop), new ClientDestroyedError())
446
448
  return
447
449
  }
448
450
 
@@ -453,7 +455,7 @@ async function connect (client) {
453
455
  ? await connectH2(client, socket)
454
456
  : await connectH1(client, socket)
455
457
  } catch (err) {
456
- socket.destroy().on('error', () => {})
458
+ socket.destroy().on('error', noop)
457
459
  throw err
458
460
  }
459
461
 
@@ -113,9 +113,9 @@ class PoolBase extends DispatcherBase {
113
113
 
114
114
  async [kClose] () {
115
115
  if (this[kQueue].isEmpty()) {
116
- return Promise.all(this[kClients].map(c => c.close()))
116
+ await Promise.all(this[kClients].map(c => c.close()))
117
117
  } else {
118
- return new Promise((resolve) => {
118
+ await new Promise((resolve) => {
119
119
  this[kClosedResolve] = resolve
120
120
  })
121
121
  }
@@ -130,7 +130,7 @@ class PoolBase extends DispatcherBase {
130
130
  item.handler.onError(err)
131
131
  }
132
132
 
133
- return Promise.all(this[kClients].map(c => c.destroy(err)))
133
+ await Promise.all(this[kClients].map(c => c.destroy(err)))
134
134
  }
135
135
 
136
136
  [kDispatch] (opts, handler) {
@@ -23,6 +23,8 @@ function defaultFactory (origin, opts) {
23
23
  return new Pool(origin, opts)
24
24
  }
25
25
 
26
+ const noop = () => {}
27
+
26
28
  class ProxyAgent extends DispatcherBase {
27
29
  constructor (opts) {
28
30
  super()
@@ -81,7 +83,7 @@ class ProxyAgent extends DispatcherBase {
81
83
  servername: this[kProxyTls]?.servername || proxyHostname
82
84
  })
83
85
  if (statusCode !== 200) {
84
- socket.on('error', () => {}).destroy()
86
+ socket.on('error', noop).destroy()
85
87
  callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
86
88
  }
87
89
  if (opts.protocol !== 'https:') {
@@ -229,7 +229,7 @@ class RetryHandler {
229
229
  return false
230
230
  }
231
231
 
232
- const { start, size, end = size } = contentRange
232
+ const { start, size, end = size - 1 } = contentRange
233
233
 
234
234
  assert(this.start === start, 'content-range mismatch')
235
235
  assert(this.end == null || this.end === end, 'content-range mismatch')
@@ -252,7 +252,7 @@ class RetryHandler {
252
252
  )
253
253
  }
254
254
 
255
- const { start, size, end = size } = range
255
+ const { start, size, end = size - 1 } = range
256
256
  assert(
257
257
  start != null && Number.isFinite(start),
258
258
  'content-range mismatch'
@@ -266,7 +266,7 @@ class RetryHandler {
266
266
  // We make our best to checkpoint the body for further range headers
267
267
  if (this.end == null) {
268
268
  const contentLength = headers['content-length']
269
- this.end = contentLength != null ? Number(contentLength) : null
269
+ this.end = contentLength != null ? Number(contentLength) - 1 : null
270
270
  }
271
271
 
272
272
  assert(Number.isFinite(this.start))
@@ -37,6 +37,7 @@ class Cache {
37
37
  webidl.illegalConstructor()
38
38
  }
39
39
 
40
+ webidl.util.markAsUncloneable(this)
40
41
  this.#relevantRequestResponseList = arguments[1]
41
42
  }
42
43
 
@@ -16,6 +16,8 @@ class CacheStorage {
16
16
  if (arguments[0] !== kConstruct) {
17
17
  webidl.illegalConstructor()
18
18
  }
19
+
20
+ webidl.util.markAsUncloneable(this)
19
21
  }
20
22
 
21
23
  async match (request, options = {}) {
@@ -105,6 +105,8 @@ class EventSource extends EventTarget {
105
105
  // 1. Let ev be a new EventSource object.
106
106
  super()
107
107
 
108
+ webidl.util.markAsUncloneable(this)
109
+
108
110
  const prefix = 'EventSource constructor'
109
111
  webidl.argumentLengthCheck(arguments, 1, prefix)
110
112
 
@@ -1,27 +1,30 @@
1
1
  'use strict'
2
2
 
3
- const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
3
+ const corsSafeListedMethods = /** @type {const} */ (['GET', 'HEAD', 'POST'])
4
4
  const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
5
5
 
6
- const nullBodyStatus = [101, 204, 205, 304]
6
+ const nullBodyStatus = /** @type {const} */ ([101, 204, 205, 304])
7
7
 
8
- const redirectStatus = [301, 302, 303, 307, 308]
8
+ const redirectStatus = /** @type {const} */ ([301, 302, 303, 307, 308])
9
9
  const redirectStatusSet = new Set(redirectStatus)
10
10
 
11
- // https://fetch.spec.whatwg.org/#block-bad-port
12
- const badPorts = [
11
+ /**
12
+ * @see https://fetch.spec.whatwg.org/#block-bad-port
13
+ */
14
+ const badPorts = /** @type {const} */ ([
13
15
  '1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79',
14
16
  '87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
15
17
  '139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
16
18
  '540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
17
19
  '2049', '3659', '4045', '4190', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6679',
18
20
  '6697', '10080'
19
- ]
20
-
21
+ ])
21
22
  const badPortsSet = new Set(badPorts)
22
23
 
23
- // https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
24
- const referrerPolicy = [
24
+ /**
25
+ * @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
26
+ */
27
+ const referrerPolicy = /** @type {const} */ ([
25
28
  '',
26
29
  'no-referrer',
27
30
  'no-referrer-when-downgrade',
@@ -31,29 +34,31 @@ const referrerPolicy = [
31
34
  'origin-when-cross-origin',
32
35
  'strict-origin-when-cross-origin',
33
36
  'unsafe-url'
34
- ]
37
+ ])
35
38
  const referrerPolicySet = new Set(referrerPolicy)
36
39
 
37
- const requestRedirect = ['follow', 'manual', 'error']
40
+ const requestRedirect = /** @type {const} */ (['follow', 'manual', 'error'])
38
41
 
39
- const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE']
42
+ const safeMethods = /** @type {const} */ (['GET', 'HEAD', 'OPTIONS', 'TRACE'])
40
43
  const safeMethodsSet = new Set(safeMethods)
41
44
 
42
- const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors']
45
+ const requestMode = /** @type {const} */ (['navigate', 'same-origin', 'no-cors', 'cors'])
43
46
 
44
- const requestCredentials = ['omit', 'same-origin', 'include']
47
+ const requestCredentials = /** @type {const} */ (['omit', 'same-origin', 'include'])
45
48
 
46
- const requestCache = [
49
+ const requestCache = /** @type {const} */ ([
47
50
  'default',
48
51
  'no-store',
49
52
  'reload',
50
53
  'no-cache',
51
54
  'force-cache',
52
55
  'only-if-cached'
53
- ]
56
+ ])
54
57
 
55
- // https://fetch.spec.whatwg.org/#request-body-header-name
56
- const requestBodyHeader = [
58
+ /**
59
+ * @see https://fetch.spec.whatwg.org/#request-body-header-name
60
+ */
61
+ const requestBodyHeader = /** @type {const} */ ([
57
62
  'content-encoding',
58
63
  'content-language',
59
64
  'content-location',
@@ -63,18 +68,22 @@ const requestBodyHeader = [
63
68
  // removed in the Headers implementation. However, undici doesn't
64
69
  // filter out headers, so we add it here.
65
70
  'content-length'
66
- ]
71
+ ])
67
72
 
68
- // https://fetch.spec.whatwg.org/#enumdef-requestduplex
69
- const requestDuplex = [
73
+ /**
74
+ * @see https://fetch.spec.whatwg.org/#enumdef-requestduplex
75
+ */
76
+ const requestDuplex = /** @type {const} */ ([
70
77
  'half'
71
- ]
78
+ ])
72
79
 
73
- // http://fetch.spec.whatwg.org/#forbidden-method
74
- const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']
80
+ /**
81
+ * @see http://fetch.spec.whatwg.org/#forbidden-method
82
+ */
83
+ const forbiddenMethods = /** @type {const} */ (['CONNECT', 'TRACE', 'TRACK'])
75
84
  const forbiddenMethodsSet = new Set(forbiddenMethods)
76
85
 
77
- const subresource = [
86
+ const subresource = /** @type {const} */ ([
78
87
  'audio',
79
88
  'audioworklet',
80
89
  'font',
@@ -87,7 +96,7 @@ const subresource = [
87
96
  'video',
88
97
  'xslt',
89
98
  ''
90
- ]
99
+ ])
91
100
  const subresourceSet = new Set(subresource)
92
101
 
93
102
  module.exports = {
@@ -14,6 +14,8 @@ const File = globalThis.File ?? NativeFile
14
14
  // https://xhr.spec.whatwg.org/#formdata
15
15
  class FormData {
16
16
  constructor (form) {
17
+ webidl.util.markAsUncloneable(this)
18
+
17
19
  if (form !== undefined) {
18
20
  throw webidl.errors.conversionFailed({
19
21
  prefix: 'FormData constructor',
@@ -359,6 +359,8 @@ class Headers {
359
359
  #headersList
360
360
 
361
361
  constructor (init = undefined) {
362
+ webidl.util.markAsUncloneable(this)
363
+
362
364
  if (init === kConstruct) {
363
365
  return
364
366
  }
@@ -2137,7 +2137,7 @@ async function httpNetworkFetch (
2137
2137
 
2138
2138
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
2139
2139
  if (codings.length !== 0 && request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
2140
- for (let i = 0; i < codings.length; ++i) {
2140
+ for (let i = codings.length - 1; i >= 0; --i) {
2141
2141
  const coding = codings[i]
2142
2142
  // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
2143
2143
  if (coding === 'x-gzip' || coding === 'gzip') {
@@ -82,6 +82,7 @@ let patchMethodWarning = false
82
82
  class Request {
83
83
  // https://fetch.spec.whatwg.org/#dom-request
84
84
  constructor (input, init = {}) {
85
+ webidl.util.markAsUncloneable(this)
85
86
  if (input === kConstruct) {
86
87
  return
87
88
  }
@@ -110,6 +110,7 @@ class Response {
110
110
 
111
111
  // https://fetch.spec.whatwg.org/#dom-response
112
112
  constructor (body = null, init = {}) {
113
+ webidl.util.markAsUncloneable(this)
113
114
  if (body === kConstruct) {
114
115
  return
115
116
  }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { types, inspect } = require('node:util')
4
+ const { markAsUncloneable } = require('node:worker_threads')
4
5
  const { toUSVString } = require('../../core/util')
5
6
 
6
7
  /** @type {import('../../../types/webidl').Webidl} */
@@ -86,6 +87,7 @@ webidl.util.Type = function (V) {
86
87
  }
87
88
  }
88
89
 
90
+ webidl.util.markAsUncloneable = markAsUncloneable || (() => {})
89
91
  // https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
90
92
  webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
91
93
  let upperBound
@@ -14,6 +14,7 @@ class MessageEvent extends Event {
14
14
  constructor (type, eventInitDict = {}) {
15
15
  if (type === kConstruct) {
16
16
  super(arguments[1], arguments[2])
17
+ webidl.util.markAsUncloneable(this)
17
18
  return
18
19
  }
19
20
 
@@ -26,6 +27,7 @@ class MessageEvent extends Event {
26
27
  super(type, eventInitDict)
27
28
 
28
29
  this.#eventInit = eventInitDict
30
+ webidl.util.markAsUncloneable(this)
29
31
  }
30
32
 
31
33
  get data () {
@@ -112,6 +114,7 @@ class CloseEvent extends Event {
112
114
  super(type, eventInitDict)
113
115
 
114
116
  this.#eventInit = eventInitDict
117
+ webidl.util.markAsUncloneable(this)
115
118
  }
116
119
 
117
120
  get wasClean () {
@@ -142,6 +145,7 @@ class ErrorEvent extends Event {
142
145
  webidl.argumentLengthCheck(arguments, 1, prefix)
143
146
 
144
147
  super(type, eventInitDict)
148
+ webidl.util.markAsUncloneable(this)
145
149
 
146
150
  type = webidl.converters.DOMString(type, prefix, 'type')
147
151
  eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {})
@@ -51,6 +51,8 @@ class WebSocket extends EventTarget {
51
51
  constructor (url, protocols = []) {
52
52
  super()
53
53
 
54
+ webidl.util.markAsUncloneable(this)
55
+
54
56
  const prefix = 'WebSocket constructor'
55
57
  webidl.argumentLengthCheck(arguments, 1, prefix)
56
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.20.0",
3
+ "version": "6.21.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": {
@@ -78,6 +78,9 @@
78
78
  "test:fuzzing": "node test/fuzzing/fuzzing.test.js",
79
79
  "test:fetch": "npm run build:node && npm run test:fetch:nobuild",
80
80
  "test:fetch:nobuild": "borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
81
+ "test:h2": "npm run test:h2:core && npm run test:h2:fetch",
82
+ "test:h2:core": "borp -p \"test/http2*.js\"",
83
+ "test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"",
81
84
  "test:interceptors": "borp -p \"test/interceptors/*.js\"",
82
85
  "test:jest": "cross-env NODE_V8_COVERAGE= jest",
83
86
  "test:unit": "borp --expose-gc -p \"test/*.js\"",
@@ -244,6 +244,7 @@ declare namespace Dispatcher {
244
244
  readonly bodyUsed: boolean;
245
245
  arrayBuffer(): Promise<ArrayBuffer>;
246
246
  blob(): Promise<Blob>;
247
+ bytes(): Promise<Uint8Array>;
247
248
  formData(): Promise<never>;
248
249
  json(): Promise<unknown>;
249
250
  text(): Promise<string>;
@@ -25,6 +25,11 @@ declare class BodyReadable extends Readable {
25
25
  */
26
26
  blob(): Promise<Blob>
27
27
 
28
+ /** Consumes and returns the body as an Uint8Array
29
+ * https://fetch.spec.whatwg.org/#dom-body-bytes
30
+ */
31
+ bytes(): Promise<Uint8Array>
32
+
28
33
  /** Consumes and returns the body as an ArrayBuffer
29
34
  * https://fetch.spec.whatwg.org/#dom-body-arraybuffer
30
35
  */
package/types/webidl.d.ts CHANGED
@@ -67,6 +67,12 @@ interface WebidlUtil {
67
67
  * Stringifies {@param V}
68
68
  */
69
69
  Stringify (V: any): string
70
+
71
+ /**
72
+ * Mark a value as uncloneable for Node.js.
73
+ * This is only effective in some newer Node.js versions.
74
+ */
75
+ markAsUncloneable (V: any): void
70
76
  }
71
77
 
72
78
  interface WebidlConverters {