undici 5.13.0 → 5.14.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.
@@ -24,8 +24,10 @@ Once you call `buildConnector`, it will return a connector function, which takes
24
24
  * **hostname** `string` (required)
25
25
  * **host** `string` (optional)
26
26
  * **protocol** `string` (required)
27
- * **port** `number` (required)
27
+ * **port** `string` (required)
28
28
  * **servername** `string` (optional)
29
+ * **localAddress** `string | null` (optional) Local address the socket should connect from.
30
+ * **httpSocket** `Socket` (optional) Establish secure connection on a given socket rather than creating a new socket. It can only be sent on TLS update.
29
31
 
30
32
  ### Basic example
31
33
 
@@ -4,6 +4,7 @@ const net = require('net')
4
4
  const assert = require('assert')
5
5
  const util = require('./util')
6
6
  const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
7
+
7
8
  let tls // include tls conditionally since it is not always available
8
9
 
9
10
  // TODO: session re-use does not wait for the first
@@ -11,15 +12,73 @@ let tls // include tls conditionally since it is not always available
11
12
  // resolve the same servername multiple times even when
12
13
  // re-use is enabled.
13
14
 
15
+ let SessionCache
16
+ if (global.FinalizationRegistry) {
17
+ SessionCache = class WeakSessionCache {
18
+ constructor (maxCachedSessions) {
19
+ this._maxCachedSessions = maxCachedSessions
20
+ this._sessionCache = new Map()
21
+ this._sessionRegistry = new global.FinalizationRegistry((key) => {
22
+ if (this._sessionCache.size < this._maxCachedSessions) {
23
+ return
24
+ }
25
+
26
+ const ref = this._sessionCache.get(key)
27
+ if (ref !== undefined && ref.deref() === undefined) {
28
+ this._sessionCache.delete(key)
29
+ }
30
+ })
31
+ }
32
+
33
+ get (sessionKey) {
34
+ const ref = this._sessionCache.get(sessionKey)
35
+ return ref ? ref.deref() : null
36
+ }
37
+
38
+ set (sessionKey, session) {
39
+ if (this._maxCachedSessions === 0) {
40
+ return
41
+ }
42
+
43
+ this._sessionCache.set(sessionKey, new WeakRef(session))
44
+ this._sessionRegistry.register(session, sessionKey)
45
+ }
46
+ }
47
+ } else {
48
+ SessionCache = class SimpleSessionCache {
49
+ constructor (maxCachedSessions) {
50
+ this._maxCachedSessions = maxCachedSessions
51
+ this._sessionCache = new Map()
52
+ }
53
+
54
+ get (sessionKey) {
55
+ return this._sessionCache.get(sessionKey)
56
+ }
57
+
58
+ set (sessionKey, session) {
59
+ if (this._maxCachedSessions === 0) {
60
+ return
61
+ }
62
+
63
+ if (this._sessionCache.size >= this._maxCachedSessions) {
64
+ // remove the oldest session
65
+ const { value: oldestKey } = this._sessionCache.keys().next()
66
+ this._sessionCache.delete(oldestKey)
67
+ }
68
+
69
+ this._sessionCache.set(sessionKey, session)
70
+ }
71
+ }
72
+ }
73
+
14
74
  function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
15
75
  if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
16
76
  throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
17
77
  }
18
78
 
19
79
  const options = { path: socketPath, ...opts }
20
- const sessionCache = new Map()
80
+ const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
21
81
  timeout = timeout == null ? 10e3 : timeout
22
- maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions
23
82
 
24
83
  return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
25
84
  let socket
@@ -47,25 +106,9 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
47
106
 
48
107
  socket
49
108
  .on('session', function (session) {
50
- // cache is disabled
51
- if (maxCachedSessions === 0) {
52
- return
53
- }
54
-
55
- if (sessionCache.size >= maxCachedSessions) {
56
- // remove the oldest session
57
- const { value: oldestKey } = sessionCache.keys().next()
58
- sessionCache.delete(oldestKey)
59
- }
60
-
109
+ // TODO (fix): Can a session become invalid once established? Don't think so?
61
110
  sessionCache.set(sessionKey, session)
62
111
  })
63
- .on('error', function (err) {
64
- if (sessionKey && err.code !== 'UND_ERR_INFO') {
65
- // TODO (fix): Only delete for session related errors.
66
- sessionCache.delete(sessionKey)
67
- }
68
- })
69
112
  } else {
70
113
  assert(!httpSocket, 'httpSocket can only be sent on TLS update')
71
114
  socket = net.connect({
package/lib/fetch/body.js CHANGED
@@ -7,17 +7,19 @@ const { FormData } = require('./formdata')
7
7
  const { kState } = require('./symbols')
8
8
  const { webidl } = require('./webidl')
9
9
  const { DOMException, structuredClone } = require('./constants')
10
- const { Blob } = require('buffer')
10
+ const { Blob, File: NativeFile } = require('buffer')
11
11
  const { kBodyUsed } = require('../core/symbols')
12
12
  const assert = require('assert')
13
13
  const { isErrored } = require('../core/util')
14
14
  const { isUint8Array, isArrayBuffer } = require('util/types')
15
- const { File } = require('./file')
15
+ const { File: UndiciFile } = require('./file')
16
16
  const { StringDecoder } = require('string_decoder')
17
17
  const { parseMIMEType, serializeAMimeType } = require('./dataURL')
18
18
 
19
- /** @type {globalThis['ReadableStream']} */
20
- let ReadableStream
19
+ let ReadableStream = globalThis.ReadableStream
20
+
21
+ /** @type {globalThis['File']} */
22
+ const File = NativeFile ?? UndiciFile
21
23
 
22
24
  // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
23
25
  function extractBody (object, keepalive = false) {
@@ -142,7 +144,33 @@ function extractBody (object, keepalive = false) {
142
144
  source = object
143
145
 
144
146
  // Set length to unclear, see html/6424 for improving this.
145
- // TODO
147
+ length = (() => {
148
+ const prefixLength = prefix.length
149
+ const boundaryLength = boundary.length
150
+ let bodyLength = 0
151
+
152
+ for (const [name, value] of object) {
153
+ if (typeof value === 'string') {
154
+ bodyLength +=
155
+ prefixLength +
156
+ Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
157
+ } else {
158
+ bodyLength +=
159
+ prefixLength +
160
+ Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : '')) +
161
+ 2 + // \r\n
162
+ `Content-Type: ${
163
+ value.type || 'application/octet-stream'
164
+ }\r\n\r\n`.length
165
+
166
+ // value is a Blob or File, and \r\n
167
+ bodyLength += value.size + 2
168
+ }
169
+ }
170
+
171
+ bodyLength += boundaryLength + 4 // --boundary--
172
+ return bodyLength
173
+ })()
146
174
 
147
175
  // Set type to `multipart/form-data; boundary=`,
148
176
  // followed by the multipart/form-data boundary string generated
@@ -348,7 +376,10 @@ function bodyMixinMethods (instance) {
348
376
  let busboy
349
377
 
350
378
  try {
351
- busboy = Busboy({ headers })
379
+ busboy = Busboy({
380
+ headers,
381
+ defParamCharset: 'utf8'
382
+ })
352
383
  } catch (err) {
353
384
  // Error due to headers:
354
385
  throw Object.assign(new TypeError(), { cause: err })
@@ -361,7 +392,7 @@ function bodyMixinMethods (instance) {
361
392
  const { filename, encoding, mimeType } = info
362
393
  const chunks = []
363
394
 
364
- if (encoding.toLowerCase() === 'base64') {
395
+ if (encoding === 'base64' || encoding.toLowerCase() === 'base64') {
365
396
  let base64chunk = ''
366
397
 
367
398
  value.on('data', (chunk) => {
package/lib/fetch/file.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { Blob } = require('buffer')
3
+ const { Blob, File: NativeFile } = require('buffer')
4
4
  const { types } = require('util')
5
5
  const { kState } = require('./symbols')
6
6
  const { isBlobLike } = require('./util')
@@ -329,11 +329,14 @@ function convertLineEndingsNative (s) {
329
329
  // rollup) will warn about circular dependencies. See:
330
330
  // https://github.com/nodejs/undici/issues/1629
331
331
  function isFileLike (object) {
332
- return object instanceof File || (
333
- object &&
334
- (typeof object.stream === 'function' ||
335
- typeof object.arrayBuffer === 'function') &&
336
- object[Symbol.toStringTag] === 'File'
332
+ return (
333
+ (NativeFile && object instanceof NativeFile) ||
334
+ object instanceof File || (
335
+ object &&
336
+ (typeof object.stream === 'function' ||
337
+ typeof object.arrayBuffer === 'function') &&
338
+ object[Symbol.toStringTag] === 'File'
339
+ )
337
340
  )
338
341
  }
339
342
 
@@ -2,9 +2,12 @@
2
2
 
3
3
  const { isBlobLike, toUSVString, makeIterator } = require('./util')
4
4
  const { kState } = require('./symbols')
5
- const { File, FileLike, isFileLike } = require('./file')
5
+ const { File: UndiciFile, FileLike, isFileLike } = require('./file')
6
6
  const { webidl } = require('./webidl')
7
- const { Blob } = require('buffer')
7
+ const { Blob, File: NativeFile } = require('buffer')
8
+
9
+ /** @type {globalThis['File']} */
10
+ const File = NativeFile ?? UndiciFile
8
11
 
9
12
  // https://xhr.spec.whatwg.org/#formdata
10
13
  class FormData {
@@ -3,7 +3,7 @@
3
3
  'use strict'
4
4
 
5
5
  const { kHeadersList } = require('../core/symbols')
6
- const { kGuard } = require('./symbols')
6
+ const { kGuard, kHeadersCaseInsensitive } = require('./symbols')
7
7
  const { kEnumerableProperty } = require('../core/util')
8
8
  const {
9
9
  makeIterator,
@@ -96,27 +96,27 @@ class HeadersList {
96
96
 
97
97
  // 1. If list contains name, then set name to the first such
98
98
  // header’s name.
99
- name = name.toLowerCase()
100
- const exists = this[kHeadersMap].get(name)
99
+ const lowercaseName = name.toLowerCase()
100
+ const exists = this[kHeadersMap].get(lowercaseName)
101
101
 
102
102
  // 2. Append (name, value) to list.
103
103
  if (exists) {
104
- this[kHeadersMap].set(name, `${exists}, ${value}`)
104
+ this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` })
105
105
  } else {
106
- this[kHeadersMap].set(name, `${value}`)
106
+ this[kHeadersMap].set(lowercaseName, { name, value })
107
107
  }
108
108
  }
109
109
 
110
110
  // https://fetch.spec.whatwg.org/#concept-header-list-set
111
111
  set (name, value) {
112
112
  this[kHeadersSortedMap] = null
113
- name = name.toLowerCase()
113
+ const lowercaseName = name.toLowerCase()
114
114
 
115
115
  // 1. If list contains name, then set the value of
116
116
  // the first such header to value and remove the
117
117
  // others.
118
118
  // 2. Otherwise, append header (name, value) to list.
119
- return this[kHeadersMap].set(name, value)
119
+ return this[kHeadersMap].set(lowercaseName, { name, value })
120
120
  }
121
121
 
122
122
  // https://fetch.spec.whatwg.org/#concept-header-list-delete
@@ -137,14 +137,26 @@ class HeadersList {
137
137
  // 2. Return the values of all headers in list whose name
138
138
  // is a byte-case-insensitive match for name,
139
139
  // separated from each other by 0x2C 0x20, in order.
140
- return this[kHeadersMap].get(name.toLowerCase()) ?? null
140
+ return this[kHeadersMap].get(name.toLowerCase())?.value ?? null
141
141
  }
142
142
 
143
143
  * [Symbol.iterator] () {
144
- for (const pair of this[kHeadersMap]) {
145
- yield pair
144
+ // use the lowercased name
145
+ for (const [name, { value }] of this[kHeadersMap]) {
146
+ yield [name, value]
146
147
  }
147
148
  }
149
+
150
+ get [kHeadersCaseInsensitive] () {
151
+ /** @type {string[]} */
152
+ const flatList = []
153
+
154
+ for (const { name, value } of this[kHeadersMap].values()) {
155
+ flatList.push(name, value)
156
+ }
157
+
158
+ return flatList
159
+ }
148
160
  }
149
161
 
150
162
  // https://fetch.spec.whatwg.org/#headers-class
@@ -39,7 +39,7 @@ const {
39
39
  readableStreamClose,
40
40
  isomorphicEncode
41
41
  } = require('./util')
42
- const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
42
+ const { kState, kHeaders, kGuard, kRealm, kHeadersCaseInsensitive } = require('./symbols')
43
43
  const assert = require('assert')
44
44
  const { safelyExtractBody } = require('./body')
45
45
  const {
@@ -61,8 +61,7 @@ const { webidl } = require('./webidl')
61
61
 
62
62
  /** @type {import('buffer').resolveObjectURL} */
63
63
  let resolveObjectURL
64
- /** @type {globalThis['ReadableStream']} */
65
- let ReadableStream
64
+ let ReadableStream = globalThis.ReadableStream
66
65
 
67
66
  const nodeVersion = process.versions.node.split('.')
68
67
  const nodeMajor = Number(nodeVersion[0])
@@ -781,8 +780,11 @@ async function mainFetch (fetchParams, recursive = false) {
781
780
  // https://fetch.spec.whatwg.org/#concept-scheme-fetch
782
781
  // given a fetch params fetchParams
783
782
  async function schemeFetch (fetchParams) {
783
+ // Note: since the connection is destroyed on redirect, which sets fetchParams to a
784
+ // cancelled state, we do not want this condition to trigger *unless* there have been
785
+ // no redirects. See https://github.com/nodejs/undici/issues/1776
784
786
  // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
785
- if (isCancelled(fetchParams)) {
787
+ if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) {
786
788
  return makeAppropriateNetworkError(fetchParams)
787
789
  }
788
790
 
@@ -840,8 +842,8 @@ async function schemeFetch (fetchParams) {
840
842
  const response = makeResponse({
841
843
  statusText: 'OK',
842
844
  headersList: [
843
- ['content-length', length],
844
- ['content-type', type]
845
+ ['content-length', { name: 'Content-Length', value: length }],
846
+ ['content-type', { name: 'Content-Type', value: type }]
845
847
  ]
846
848
  })
847
849
 
@@ -870,7 +872,7 @@ async function schemeFetch (fetchParams) {
870
872
  return makeResponse({
871
873
  statusText: 'OK',
872
874
  headersList: [
873
- ['content-type', mimeType]
875
+ ['content-type', { name: 'Content-Type', value: mimeType }]
874
876
  ],
875
877
  body: safelyExtractBody(dataURLStruct.body)[0]
876
878
  })
@@ -1135,12 +1137,12 @@ async function httpRedirectFetch (fetchParams, response) {
1135
1137
  return makeNetworkError('URL scheme must be a HTTP(S) scheme')
1136
1138
  }
1137
1139
 
1138
- // 7. If request’s redirect count is twenty, return a network error.
1140
+ // 7. If request’s redirect count is 20, then return a network error.
1139
1141
  if (request.redirectCount === 20) {
1140
1142
  return makeNetworkError('redirect count exceeded')
1141
1143
  }
1142
1144
 
1143
- // 8. Increase request’s redirect count by one.
1145
+ // 8. Increase request’s redirect count by 1.
1144
1146
  request.redirectCount += 1
1145
1147
 
1146
1148
  // 9. If request’s mode is "cors", locationURL includes credentials, and
@@ -1195,36 +1197,44 @@ async function httpRedirectFetch (fetchParams, response) {
1195
1197
  }
1196
1198
  }
1197
1199
 
1198
- // 13. If request’s body is non-null, then set request’s body to the first return
1200
+ // 13. If request’s current URL’s origin is not same origin with locationURL’s
1201
+ // origin, then for each headerName of CORS non-wildcard request-header name,
1202
+ // delete headerName from request’s header list.
1203
+ if (!sameOrigin(requestCurrentURL(request), locationURL)) {
1204
+ // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
1205
+ request.headersList.delete('authorization')
1206
+ }
1207
+
1208
+ // 14. If request’s body is non-null, then set request’s body to the first return
1199
1209
  // value of safely extracting request’s body’s source.
1200
1210
  if (request.body != null) {
1201
1211
  assert(request.body.source)
1202
1212
  request.body = safelyExtractBody(request.body.source)[0]
1203
1213
  }
1204
1214
 
1205
- // 14. Let timingInfo be fetchParams’s timing info.
1215
+ // 15. Let timingInfo be fetchParams’s timing info.
1206
1216
  const timingInfo = fetchParams.timingInfo
1207
1217
 
1208
- // 15. Set timingInfo’s redirect end time and post-redirect start time to the
1218
+ // 16. Set timingInfo’s redirect end time and post-redirect start time to the
1209
1219
  // coarsened shared current time given fetchParams’s cross-origin isolated
1210
1220
  // capability.
1211
1221
  timingInfo.redirectEndTime = timingInfo.postRedirectStartTime =
1212
1222
  coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
1213
1223
 
1214
- // 16. If timingInfo’s redirect start time is 0, then set timingInfo’s
1224
+ // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s
1215
1225
  // redirect start time to timingInfo’s start time.
1216
1226
  if (timingInfo.redirectStartTime === 0) {
1217
1227
  timingInfo.redirectStartTime = timingInfo.startTime
1218
1228
  }
1219
1229
 
1220
- // 17. Append locationURL to request’s URL list.
1230
+ // 18. Append locationURL to request’s URL list.
1221
1231
  request.urlList.push(locationURL)
1222
1232
 
1223
- // 18. Invoke set request’s referrer policy on redirect on request and
1233
+ // 19. Invoke set request’s referrer policy on redirect on request and
1224
1234
  // actualResponse.
1225
1235
  setRequestReferrerPolicyOnRedirect(request, actualResponse)
1226
1236
 
1227
- // 19. Return the result of running main fetch given fetchParams and true.
1237
+ // 20. Return the result of running main fetch given fetchParams and true.
1228
1238
  return mainFetch(fetchParams, true)
1229
1239
  }
1230
1240
 
@@ -1930,7 +1940,7 @@ async function httpNetworkFetch (
1930
1940
  origin: url.origin,
1931
1941
  method: request.method,
1932
1942
  body: fetchParams.controller.dispatcher.isMockActive ? request.body && request.body.source : body,
1933
- headers: [...request.headersList].flat(),
1943
+ headers: request.headersList[kHeadersCaseInsensitive],
1934
1944
  maxRedirections: 0,
1935
1945
  bodyTimeout: 300_000,
1936
1946
  headersTimeout: 300_000
@@ -29,7 +29,7 @@ const { URLSerializer } = require('./dataURL')
29
29
  const { kHeadersList } = require('../core/symbols')
30
30
  const assert = require('assert')
31
31
 
32
- let TransformStream
32
+ let TransformStream = globalThis.TransformStream
33
33
 
34
34
  const kInit = Symbol('init')
35
35
 
@@ -436,7 +436,7 @@ function makeAppropriateNetworkError (fetchParams) {
436
436
  // otherwise return a network error.
437
437
  return isAborted(fetchParams)
438
438
  ? makeNetworkError(new DOMException('The operation was aborted.', 'AbortError'))
439
- : makeNetworkError(fetchParams.controller.terminated.reason)
439
+ : makeNetworkError('Request was cancelled.')
440
440
  }
441
441
 
442
442
  // https://whatpr.org/fetch/1392.html#initialize-a-response
@@ -6,5 +6,6 @@ module.exports = {
6
6
  kSignal: Symbol('signal'),
7
7
  kState: Symbol('state'),
8
8
  kGuard: Symbol('guard'),
9
- kRealm: Symbol('realm')
9
+ kRealm: Symbol('realm'),
10
+ kHeadersCaseInsensitive: Symbol('headers case insensitive')
10
11
  }
package/lib/fetch/util.js CHANGED
@@ -863,6 +863,8 @@ function isReadableStreamLike (stream) {
863
863
  )
864
864
  }
865
865
 
866
+ const MAXIMUM_ARGUMENT_LENGTH = 65535
867
+
866
868
  /**
867
869
  * @see https://infra.spec.whatwg.org/#isomorphic-decode
868
870
  * @param {number[]|Uint8Array} input
@@ -871,13 +873,12 @@ function isomorphicDecode (input) {
871
873
  // 1. To isomorphic decode a byte sequence input, return a string whose code point
872
874
  // length is equal to input’s length and whose code points have the same values
873
875
  // as the values of input’s bytes, in the same order.
874
- let output = ''
875
876
 
876
- for (let i = 0; i < input.length; i++) {
877
- output += String.fromCharCode(input[i])
877
+ if (input.length < MAXIMUM_ARGUMENT_LENGTH) {
878
+ return String.fromCharCode(...input)
878
879
  }
879
880
 
880
- return output
881
+ return input.reduce((previous, current) => previous + String.fromCharCode(current), '')
881
882
  }
882
883
 
883
884
  /**
@@ -182,8 +182,13 @@ class FileReader extends EventTarget {
182
182
  set onloadend (fn) {
183
183
  webidl.brandCheck(this, FileReader)
184
184
 
185
+ if (this[kEvents].loadend) {
186
+ this.removeEventListener('loadend', this[kEvents].loadend)
187
+ }
188
+
185
189
  if (typeof fn === 'function') {
186
190
  this[kEvents].loadend = fn
191
+ this.addEventListener('loadend', fn)
187
192
  } else {
188
193
  this[kEvents].loadend = null
189
194
  }
@@ -198,8 +203,13 @@ class FileReader extends EventTarget {
198
203
  set onerror (fn) {
199
204
  webidl.brandCheck(this, FileReader)
200
205
 
206
+ if (this[kEvents].error) {
207
+ this.removeEventListener('error', this[kEvents].error)
208
+ }
209
+
201
210
  if (typeof fn === 'function') {
202
211
  this[kEvents].error = fn
212
+ this.addEventListener('error', fn)
203
213
  } else {
204
214
  this[kEvents].error = null
205
215
  }
@@ -214,8 +224,13 @@ class FileReader extends EventTarget {
214
224
  set onloadstart (fn) {
215
225
  webidl.brandCheck(this, FileReader)
216
226
 
227
+ if (this[kEvents].loadstart) {
228
+ this.removeEventListener('loadstart', this[kEvents].loadstart)
229
+ }
230
+
217
231
  if (typeof fn === 'function') {
218
232
  this[kEvents].loadstart = fn
233
+ this.addEventListener('loadstart', fn)
219
234
  } else {
220
235
  this[kEvents].loadstart = null
221
236
  }
@@ -230,8 +245,13 @@ class FileReader extends EventTarget {
230
245
  set onprogress (fn) {
231
246
  webidl.brandCheck(this, FileReader)
232
247
 
248
+ if (this[kEvents].progress) {
249
+ this.removeEventListener('progress', this[kEvents].progress)
250
+ }
251
+
233
252
  if (typeof fn === 'function') {
234
253
  this[kEvents].progress = fn
254
+ this.addEventListener('progress', fn)
235
255
  } else {
236
256
  this[kEvents].progress = null
237
257
  }
@@ -246,8 +266,13 @@ class FileReader extends EventTarget {
246
266
  set onload (fn) {
247
267
  webidl.brandCheck(this, FileReader)
248
268
 
269
+ if (this[kEvents].load) {
270
+ this.removeEventListener('load', this[kEvents].load)
271
+ }
272
+
249
273
  if (typeof fn === 'function') {
250
274
  this[kEvents].load = fn
275
+ this.addEventListener('load', fn)
251
276
  } else {
252
277
  this[kEvents].load = null
253
278
  }
@@ -262,8 +287,13 @@ class FileReader extends EventTarget {
262
287
  set onabort (fn) {
263
288
  webidl.brandCheck(this, FileReader)
264
289
 
290
+ if (this[kEvents].abort) {
291
+ this.removeEventListener('abort', this[kEvents].abort)
292
+ }
293
+
265
294
  if (typeof fn === 'function') {
266
295
  this[kEvents].abort = fn
296
+ this.addEventListener('abort', fn)
267
297
  } else {
268
298
  this[kEvents].abort = null
269
299
  }
@@ -191,25 +191,19 @@ function readOperation (fr, blob, type, encodingName) {
191
191
 
192
192
  /**
193
193
  * @see https://w3c.github.io/FileAPI/#fire-a-progress-event
194
+ * @see https://dom.spec.whatwg.org/#concept-event-fire
194
195
  * @param {string} e The name of the event
195
196
  * @param {import('./filereader').FileReader} reader
196
197
  */
197
198
  function fireAProgressEvent (e, reader) {
199
+ // The progress event e does not bubble. e.bubbles must be false
200
+ // The progress event e is NOT cancelable. e.cancelable must be false
198
201
  const event = new ProgressEvent(e, {
199
202
  bubbles: false,
200
203
  cancelable: false
201
204
  })
202
205
 
203
206
  reader.dispatchEvent(event)
204
- try {
205
- // eslint-disable-next-line no-useless-call
206
- reader[`on${e}`]?.call(reader, event)
207
- } catch (err) {
208
- // Prevent the error from being swallowed
209
- queueMicrotask(() => {
210
- throw err
211
- })
212
- }
213
207
  }
214
208
 
215
209
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "5.13.0",
3
+ "version": "5.14.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": {
@@ -89,12 +89,12 @@
89
89
  "proxy": "^1.0.2",
90
90
  "proxyquire": "^2.1.3",
91
91
  "semver": "^7.3.5",
92
- "sinon": "^14.0.0",
92
+ "sinon": "^15.0.0",
93
93
  "snazzy": "^9.0.0",
94
94
  "standard": "^17.0.0",
95
95
  "table": "^6.8.0",
96
96
  "tap": "^16.1.0",
97
- "tsd": "^0.24.1",
97
+ "tsd": "^0.25.0",
98
98
  "typescript": "^4.8.4",
99
99
  "wait-on": "^6.0.0"
100
100
  },
@@ -16,8 +16,10 @@ declare namespace buildConnector {
16
16
  hostname: string
17
17
  host?: string
18
18
  protocol: string
19
- port: number
19
+ port: string
20
20
  servername?: string
21
+ localAddress?: string | null
22
+ httpSocket?: Socket
21
23
  }
22
24
 
23
25
  export type Callback = (...args: CallbackArgs) => void