undici 6.11.1 → 6.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.
@@ -969,6 +969,12 @@ Parameters:
969
969
  * **targets** `Array<Dispatcher>`
970
970
  * **error** `Error`
971
971
 
972
+ Emitted when the dispatcher has been disconnected from the origin.
973
+
974
+ > **Note**: For HTTP/2, this event is also emitted when the dispatcher has received the [GOAWAY Frame](https://webconcepts.info/concepts/http2-frame-type/0x7) with an Error with the message `HTTP/2: "GOAWAY" frame received` and the code `UND_ERR_INFO`.
975
+ > Due to nature of the protocol of using binary frames, it is possible that requests gets hanging as a frame can be received between the `HEADER` and `DATA` frames.
976
+ > It is recommended to handle this event and close the dispatcher to create a new HTTP/2 session.
977
+
972
978
  ### Event: `'connectionError'`
973
979
 
974
980
  Parameters:
@@ -8,11 +8,14 @@ function abort (self) {
8
8
  if (self.abort) {
9
9
  self.abort(self[kSignal]?.reason)
10
10
  } else {
11
- self.onError(self[kSignal]?.reason ?? new RequestAbortedError())
11
+ self.reason = self[kSignal]?.reason ?? new RequestAbortedError()
12
12
  }
13
+ removeSignal(self)
13
14
  }
14
15
 
15
16
  function addSignal (self, signal) {
17
+ self.reason = null
18
+
16
19
  self[kSignal] = null
17
20
  self[kListener] = null
18
21
 
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
3
4
  const { AsyncResource } = require('node:async_hooks')
4
- const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
5
+ const { InvalidArgumentError, SocketError } = require('../core/errors')
5
6
  const util = require('../core/util')
6
7
  const { addSignal, removeSignal } = require('./abort-signal')
7
8
 
@@ -32,10 +33,13 @@ class ConnectHandler extends AsyncResource {
32
33
  }
33
34
 
34
35
  onConnect (abort, context) {
35
- if (!this.callback) {
36
- throw new RequestAbortedError()
36
+ if (this.reason) {
37
+ abort(this.reason)
38
+ return
37
39
  }
38
40
 
41
+ assert(this.callback)
42
+
39
43
  this.abort = abort
40
44
  this.context = context
41
45
  }
@@ -147,12 +147,14 @@ class PipelineHandler extends AsyncResource {
147
147
  onConnect (abort, context) {
148
148
  const { ret, res } = this
149
149
 
150
- assert(!res, 'pipeline cannot be retried')
151
-
152
- if (ret.destroyed) {
153
- throw new RequestAbortedError()
150
+ if (this.reason) {
151
+ abort(this.reason)
152
+ return
154
153
  }
155
154
 
155
+ assert(!res, 'pipeline cannot be retried')
156
+ assert(!ret.destroyed)
157
+
156
158
  this.abort = abort
157
159
  this.context = context
158
160
  }
@@ -1,10 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
3
4
  const { Readable } = require('./readable')
4
- const {
5
- InvalidArgumentError,
6
- RequestAbortedError
7
- } = require('../core/errors')
5
+ const { InvalidArgumentError } = require('../core/errors')
8
6
  const util = require('../core/util')
9
7
  const { getResolveErrorBodyCallback } = require('./util')
10
8
  const { AsyncResource } = require('node:async_hooks')
@@ -69,10 +67,13 @@ class RequestHandler extends AsyncResource {
69
67
  }
70
68
 
71
69
  onConnect (abort, context) {
72
- if (!this.callback) {
73
- throw new RequestAbortedError()
70
+ if (this.reason) {
71
+ abort(this.reason)
72
+ return
74
73
  }
75
74
 
75
+ assert(this.callback)
76
+
76
77
  this.abort = abort
77
78
  this.context = context
78
79
  }
@@ -1,11 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
3
4
  const { finished, PassThrough } = require('node:stream')
4
- const {
5
- InvalidArgumentError,
6
- InvalidReturnValueError,
7
- RequestAbortedError
8
- } = require('../core/errors')
5
+ const { InvalidArgumentError, InvalidReturnValueError } = require('../core/errors')
9
6
  const util = require('../core/util')
10
7
  const { getResolveErrorBodyCallback } = require('./util')
11
8
  const { AsyncResource } = require('node:async_hooks')
@@ -70,10 +67,13 @@ class StreamHandler extends AsyncResource {
70
67
  }
71
68
 
72
69
  onConnect (abort, context) {
73
- if (!this.callback) {
74
- throw new RequestAbortedError()
70
+ if (this.reason) {
71
+ abort(this.reason)
72
+ return
75
73
  }
76
74
 
75
+ assert(this.callback)
76
+
77
77
  this.abort = abort
78
78
  this.context = context
79
79
  }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
3
+ const { InvalidArgumentError, SocketError } = require('../core/errors')
4
4
  const { AsyncResource } = require('node:async_hooks')
5
5
  const util = require('../core/util')
6
6
  const { addSignal, removeSignal } = require('./abort-signal')
@@ -34,10 +34,13 @@ class UpgradeHandler extends AsyncResource {
34
34
  }
35
35
 
36
36
  onConnect (abort, context) {
37
- if (!this.callback) {
38
- throw new RequestAbortedError()
37
+ if (this.reason) {
38
+ abort(this.reason)
39
+ return
39
40
  }
40
41
 
42
+ assert(this.callback)
43
+
41
44
  this.abort = abort
42
45
  this.context = null
43
46
  }
package/lib/core/util.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
- const { kDestroyed, kBodyUsed } = require('./symbols')
4
+ const { kDestroyed, kBodyUsed, kListeners } = require('./symbols')
5
5
  const { IncomingMessage } = require('node:http')
6
6
  const stream = require('node:stream')
7
7
  const net = require('node:net')
@@ -22,13 +22,20 @@ function isStream (obj) {
22
22
 
23
23
  // based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
24
24
  function isBlobLike (object) {
25
- return (Blob && object instanceof Blob) || (
26
- object &&
27
- typeof object === 'object' &&
28
- (typeof object.stream === 'function' ||
29
- typeof object.arrayBuffer === 'function') &&
30
- /^(Blob|File)$/.test(object[Symbol.toStringTag])
31
- )
25
+ if (object === null) {
26
+ return false
27
+ } else if (object instanceof Blob) {
28
+ return true
29
+ } else if (typeof object !== 'object') {
30
+ return false
31
+ } else {
32
+ const sTag = object[Symbol.toStringTag]
33
+
34
+ return (sTag === 'Blob' || sTag === 'File') && (
35
+ ('stream' in object && typeof object.stream === 'function') ||
36
+ ('arrayBuffer' in object && typeof object.arrayBuffer === 'function')
37
+ )
38
+ }
32
39
  }
33
40
 
34
41
  function buildURL (url, queryParams) {
@@ -534,6 +541,29 @@ function parseRangeHeader (range) {
534
541
  : null
535
542
  }
536
543
 
544
+ function addListener (obj, name, listener) {
545
+ const listeners = (obj[kListeners] ??= [])
546
+ listeners.push([name, listener])
547
+ obj.on(name, listener)
548
+ return obj
549
+ }
550
+
551
+ function removeAllListeners (obj) {
552
+ for (const [name, listener] of obj[kListeners] ?? []) {
553
+ obj.removeListener(name, listener)
554
+ }
555
+ obj[kListeners] = null
556
+ }
557
+
558
+ function errorRequest (client, request, err) {
559
+ try {
560
+ request.onError(err)
561
+ assert(request.aborted)
562
+ } catch (err) {
563
+ client.emit('error', err)
564
+ }
565
+ }
566
+
537
567
  const kEnumerableProperty = Object.create(null)
538
568
  kEnumerableProperty.enumerable = true
539
569
 
@@ -556,6 +586,9 @@ module.exports = {
556
586
  isDestroyed,
557
587
  headerNameToString,
558
588
  bufferToLowerCasedHeaderName,
589
+ addListener,
590
+ removeAllListeners,
591
+ errorRequest,
559
592
  parseRawHeaders,
560
593
  parseHeaders,
561
594
  parseKeepAliveTimeout,
@@ -47,7 +47,6 @@ const {
47
47
  kMaxRequests,
48
48
  kCounter,
49
49
  kMaxResponseSize,
50
- kListeners,
51
50
  kOnError,
52
51
  kResume,
53
52
  kHTTPContext
@@ -56,23 +55,11 @@ const {
56
55
  const constants = require('../llhttp/constants.js')
57
56
  const EMPTY_BUF = Buffer.alloc(0)
58
57
  const FastBuffer = Buffer[Symbol.species]
58
+ const addListener = util.addListener
59
+ const removeAllListeners = util.removeAllListeners
59
60
 
60
61
  let extractBody
61
62
 
62
- function addListener (obj, name, listener) {
63
- const listeners = (obj[kListeners] ??= [])
64
- listeners.push([name, listener])
65
- obj.on(name, listener)
66
- return obj
67
- }
68
-
69
- function removeAllListeners (obj) {
70
- for (const [name, listener] of obj[kListeners] ?? []) {
71
- obj.removeListener(name, listener)
72
- }
73
- obj[kListeners] = null
74
- }
75
-
76
63
  async function lazyllhttp () {
77
64
  const llhttpWasmData = process.env.JEST_WORKER_ID ? require('../llhttp/llhttp-wasm.js') : undefined
78
65
 
@@ -719,14 +706,14 @@ async function connectH1 (client, socket) {
719
706
  const requests = client[kQueue].splice(client[kRunningIdx])
720
707
  for (let i = 0; i < requests.length; i++) {
721
708
  const request = requests[i]
722
- errorRequest(client, request, err)
709
+ util.errorRequest(client, request, err)
723
710
  }
724
711
  } else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') {
725
712
  // Fail head of pipeline.
726
713
  const request = client[kQueue][client[kRunningIdx]]
727
714
  client[kQueue][client[kRunningIdx]++] = null
728
715
 
729
- errorRequest(client, request, err)
716
+ util.errorRequest(client, request, err)
730
717
  }
731
718
 
732
719
  client[kPendingIdx] = client[kRunningIdx]
@@ -831,15 +818,6 @@ function resumeH1 (client) {
831
818
  }
832
819
  }
833
820
 
834
- function errorRequest (client, request, err) {
835
- try {
836
- request.onError(err)
837
- assert(request.aborted)
838
- } catch (err) {
839
- client.emit('error', err)
840
- }
841
- }
842
-
843
821
  // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
844
822
  function shouldSendContentLength (method) {
845
823
  return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
@@ -906,7 +884,7 @@ function writeH1 (client, request) {
906
884
  // A user agent may send a Content-Length header with 0 value, this should be allowed.
907
885
  if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) {
908
886
  if (client[kStrictContentLength]) {
909
- errorRequest(client, request, new RequestContentLengthMismatchError())
887
+ util.errorRequest(client, request, new RequestContentLengthMismatchError())
910
888
  return false
911
889
  }
912
890
 
@@ -915,22 +893,24 @@ function writeH1 (client, request) {
915
893
 
916
894
  const socket = client[kSocket]
917
895
 
918
- try {
919
- request.onConnect((err) => {
920
- if (request.aborted || request.completed) {
921
- return
922
- }
896
+ const abort = (err) => {
897
+ if (request.aborted || request.completed) {
898
+ return
899
+ }
923
900
 
924
- errorRequest(client, request, err || new RequestAbortedError())
901
+ util.errorRequest(client, request, err || new RequestAbortedError())
925
902
 
926
- util.destroy(socket, new InformationalError('aborted'))
927
- })
903
+ util.destroy(body)
904
+ util.destroy(socket, new InformationalError('aborted'))
905
+ }
906
+
907
+ try {
908
+ request.onConnect(abort)
928
909
  } catch (err) {
929
- errorRequest(client, request, err)
910
+ util.errorRequest(client, request, err)
930
911
  }
931
912
 
932
913
  if (request.aborted) {
933
- util.destroy(body)
934
914
  return false
935
915
  }
936
916
 
@@ -998,35 +978,19 @@ function writeH1 (client, request) {
998
978
 
999
979
  /* istanbul ignore else: assertion */
1000
980
  if (!body || bodyLength === 0) {
1001
- if (contentLength === 0) {
1002
- socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1')
1003
- } else {
1004
- assert(contentLength === null, 'no body must not have content length')
1005
- socket.write(`${header}\r\n`, 'latin1')
1006
- }
1007
- request.onRequestSent()
981
+ writeBuffer({ abort, body: null, client, request, socket, contentLength, header, expectsPayload })
1008
982
  } else if (util.isBuffer(body)) {
1009
- assert(contentLength === body.byteLength, 'buffer body must have content length')
1010
-
1011
- socket.cork()
1012
- socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
1013
- socket.write(body)
1014
- socket.uncork()
1015
- request.onBodySent(body)
1016
- request.onRequestSent()
1017
- if (!expectsPayload) {
1018
- socket[kReset] = true
1019
- }
983
+ writeBuffer({ abort, body, client, request, socket, contentLength, header, expectsPayload })
1020
984
  } else if (util.isBlobLike(body)) {
1021
985
  if (typeof body.stream === 'function') {
1022
- writeIterable({ body: body.stream(), client, request, socket, contentLength, header, expectsPayload })
986
+ writeIterable({ abort, body: body.stream(), client, request, socket, contentLength, header, expectsPayload })
1023
987
  } else {
1024
- writeBlob({ body, client, request, socket, contentLength, header, expectsPayload })
988
+ writeBlob({ abort, body, client, request, socket, contentLength, header, expectsPayload })
1025
989
  }
1026
990
  } else if (util.isStream(body)) {
1027
- writeStream({ body, client, request, socket, contentLength, header, expectsPayload })
991
+ writeStream({ abort, body, client, request, socket, contentLength, header, expectsPayload })
1028
992
  } else if (util.isIterable(body)) {
1029
- writeIterable({ body, client, request, socket, contentLength, header, expectsPayload })
993
+ writeIterable({ abort, body, client, request, socket, contentLength, header, expectsPayload })
1030
994
  } else {
1031
995
  assert(false)
1032
996
  }
@@ -1034,12 +998,12 @@ function writeH1 (client, request) {
1034
998
  return true
1035
999
  }
1036
1000
 
1037
- function writeStream ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
1001
+ function writeStream ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1038
1002
  assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
1039
1003
 
1040
1004
  let finished = false
1041
1005
 
1042
- const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
1006
+ const writer = new AsyncWriter({ abort, socket, request, contentLength, client, expectsPayload, header })
1043
1007
 
1044
1008
  const onData = function (chunk) {
1045
1009
  if (finished) {
@@ -1137,7 +1101,37 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
1137
1101
  }
1138
1102
  }
1139
1103
 
1140
- async function writeBlob ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
1104
+ async function writeBuffer ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1105
+ try {
1106
+ if (!body) {
1107
+ if (contentLength === 0) {
1108
+ socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1')
1109
+ } else {
1110
+ assert(contentLength === null, 'no body must not have content length')
1111
+ socket.write(`${header}\r\n`, 'latin1')
1112
+ }
1113
+ } else if (util.isBuffer(body)) {
1114
+ assert(contentLength === body.byteLength, 'buffer body must have content length')
1115
+
1116
+ socket.cork()
1117
+ socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
1118
+ socket.write(body)
1119
+ socket.uncork()
1120
+ request.onBodySent(body)
1121
+
1122
+ if (!expectsPayload) {
1123
+ socket[kReset] = true
1124
+ }
1125
+ }
1126
+ request.onRequestSent()
1127
+
1128
+ client[kResume]()
1129
+ } catch (err) {
1130
+ abort(err)
1131
+ }
1132
+ }
1133
+
1134
+ async function writeBlob ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1141
1135
  assert(contentLength === body.size, 'blob body must have content length')
1142
1136
 
1143
1137
  try {
@@ -1161,11 +1155,11 @@ async function writeBlob ({ h2stream, body, client, request, socket, contentLeng
1161
1155
 
1162
1156
  client[kResume]()
1163
1157
  } catch (err) {
1164
- util.destroy(socket, err)
1158
+ abort(err)
1165
1159
  }
1166
1160
  }
1167
1161
 
1168
- async function writeIterable ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) {
1162
+ async function writeIterable ({ abort, body, client, request, socket, contentLength, header, expectsPayload }) {
1169
1163
  assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
1170
1164
 
1171
1165
  let callback = null
@@ -1191,7 +1185,7 @@ async function writeIterable ({ h2stream, body, client, request, socket, content
1191
1185
  .on('close', onDrain)
1192
1186
  .on('drain', onDrain)
1193
1187
 
1194
- const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header })
1188
+ const writer = new AsyncWriter({ abort, socket, request, contentLength, client, expectsPayload, header })
1195
1189
  try {
1196
1190
  // It's up to the user to somehow abort the async iterable.
1197
1191
  for await (const chunk of body) {
@@ -1215,7 +1209,7 @@ async function writeIterable ({ h2stream, body, client, request, socket, content
1215
1209
  }
1216
1210
 
1217
1211
  class AsyncWriter {
1218
- constructor ({ socket, request, contentLength, client, expectsPayload, header }) {
1212
+ constructor ({ abort, socket, request, contentLength, client, expectsPayload, header }) {
1219
1213
  this.socket = socket
1220
1214
  this.request = request
1221
1215
  this.contentLength = contentLength
@@ -1223,6 +1217,7 @@ class AsyncWriter {
1223
1217
  this.bytesWritten = 0
1224
1218
  this.expectsPayload = expectsPayload
1225
1219
  this.header = header
1220
+ this.abort = abort
1226
1221
 
1227
1222
  socket[kWriting] = true
1228
1223
  }
@@ -1338,13 +1333,13 @@ class AsyncWriter {
1338
1333
  }
1339
1334
 
1340
1335
  destroy (err) {
1341
- const { socket, client } = this
1336
+ const { socket, client, abort } = this
1342
1337
 
1343
1338
  socket[kWriting] = false
1344
1339
 
1345
1340
  if (err) {
1346
1341
  assert(client[kRunning] <= 1, 'pipeline should only contain this request')
1347
- util.destroy(socket, err)
1342
+ abort(err)
1348
1343
  }
1349
1344
  }
1350
1345
  }
@@ -22,7 +22,6 @@ const {
22
22
  kSocket,
23
23
  kStrictContentLength,
24
24
  kOnError,
25
- // HTTP2
26
25
  kMaxConcurrentStreams,
27
26
  kHTTP2Session,
28
27
  kResume
@@ -55,14 +54,20 @@ const {
55
54
  } = http2
56
55
 
57
56
  function parseH2Headers (headers) {
58
- // set-cookie is always an array. Duplicates are added to the array.
59
- // For duplicate cookie headers, the values are joined together with '; '.
60
- headers = Object.entries(headers).flat(2)
61
-
62
57
  const result = []
63
58
 
64
- for (const header of headers) {
65
- result.push(Buffer.from(header))
59
+ for (const [name, value] of Object.entries(headers)) {
60
+ // h2 may concat the header value by array
61
+ // e.g. Set-Cookie
62
+ if (Array.isArray(value)) {
63
+ for (const subvalue of value) {
64
+ // we need to provide each header value of header name
65
+ // because the headers handler expect name-value pair
66
+ result.push(Buffer.from(name), Buffer.from(subvalue))
67
+ }
68
+ } else {
69
+ result.push(Buffer.from(name), Buffer.from(value))
70
+ }
66
71
  }
67
72
 
68
73
  return result
@@ -86,16 +91,18 @@ async function connectH2 (client, socket) {
86
91
  session[kOpenStreams] = 0
87
92
  session[kClient] = client
88
93
  session[kSocket] = socket
89
- session.on('error', onHttp2SessionError)
90
- session.on('frameError', onHttp2FrameError)
91
- session.on('end', onHttp2SessionEnd)
92
- session.on('goaway', onHTTP2GoAway)
93
- session.on('close', function () {
94
+
95
+ util.addListener(session, 'error', onHttp2SessionError)
96
+ util.addListener(session, 'frameError', onHttp2FrameError)
97
+ util.addListener(session, 'end', onHttp2SessionEnd)
98
+ util.addListener(session, 'goaway', onHTTP2GoAway)
99
+ util.addListener(session, 'close', function () {
94
100
  const { [kClient]: client } = this
95
101
 
96
- const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
102
+ const err = this[kSocket][kError] || new SocketError('closed', util.getSocketInfo(this))
97
103
 
98
104
  client[kSocket] = null
105
+ client[kHTTP2Session] = null
99
106
 
100
107
  assert(client[kPending] === 0)
101
108
 
@@ -103,7 +110,7 @@ async function connectH2 (client, socket) {
103
110
  const requests = client[kQueue].splice(client[kRunningIdx])
104
111
  for (let i = 0; i < requests.length; i++) {
105
112
  const request = requests[i]
106
- errorRequest(client, request, err)
113
+ util.errorRequest(client, request, err)
107
114
  }
108
115
 
109
116
  client[kPendingIdx] = client[kRunningIdx]
@@ -114,19 +121,21 @@ async function connectH2 (client, socket) {
114
121
 
115
122
  client[kResume]()
116
123
  })
124
+
117
125
  session.unref()
118
126
 
119
127
  client[kHTTP2Session] = session
120
128
  socket[kHTTP2Session] = session
121
129
 
122
- socket.on('error', function (err) {
130
+ util.addListener(socket, 'error', function (err) {
123
131
  assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
124
132
 
125
133
  this[kError] = err
126
134
 
127
135
  this[kClient][kOnError](err)
128
136
  })
129
- socket.on('end', function () {
137
+
138
+ util.addListener(socket, 'end', function () {
130
139
  util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
131
140
  })
132
141
 
@@ -166,67 +175,42 @@ function onHttp2SessionError (err) {
166
175
  assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
167
176
 
168
177
  this[kSocket][kError] = err
169
-
170
178
  this[kClient][kOnError](err)
171
179
  }
172
180
 
173
181
  function onHttp2FrameError (type, code, id) {
174
- const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
175
-
176
182
  if (id === 0) {
183
+ const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
177
184
  this[kSocket][kError] = err
178
185
  this[kClient][kOnError](err)
179
186
  }
180
187
  }
181
188
 
182
189
  function onHttp2SessionEnd () {
183
- this.destroy(new SocketError('other side closed'))
184
- util.destroy(this[kSocket], new SocketError('other side closed'))
190
+ const err = new SocketError('other side closed', util.getSocketInfo(this[kSocket]))
191
+ this.destroy(err)
192
+ util.destroy(this[kSocket], err)
185
193
  }
186
194
 
195
+ /**
196
+ * This is the root cause of #3011
197
+ * We need to handle GOAWAY frames properly, and trigger the session close
198
+ * along with the socket right away
199
+ * Find a way to trigger the close cycle from here on.
200
+ */
187
201
  function onHTTP2GoAway (code) {
188
- const client = this[kClient]
189
202
  const err = new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${code}`)
190
- client[kSocket] = null
191
- client[kHTTP2Session] = null
192
203
 
193
- if (client.destroyed) {
194
- assert(this[kPending] === 0)
195
-
196
- // Fail entire queue.
197
- const requests = client[kQueue].splice(client[kRunningIdx])
198
- for (let i = 0; i < requests.length; i++) {
199
- const request = requests[i]
200
- errorRequest(this, request, err)
201
- }
202
- } else if (client[kRunning] > 0) {
203
- // Fail head of pipeline.
204
- const request = client[kQueue][client[kRunningIdx]]
205
- client[kQueue][client[kRunningIdx]++] = null
206
-
207
- errorRequest(client, request, err)
208
- }
209
-
210
- client[kPendingIdx] = client[kRunningIdx]
211
-
212
- assert(client[kRunning] === 0)
213
-
214
- client.emit('disconnect',
215
- client[kUrl],
216
- [client],
217
- err
218
- )
219
-
220
- client[kResume]()
221
- }
204
+ // We need to trigger the close cycle right away
205
+ // We need to destroy the session and the socket
206
+ // Requests should be failed with the error after the current one is handled
207
+ this[kSocket][kError] = err
208
+ this[kClient][kOnError](err)
222
209
 
223
- function errorRequest (client, request, err) {
224
- try {
225
- request.onError(err)
226
- assert(request.aborted)
227
- } catch (err) {
228
- client.emit('error', err)
229
- }
210
+ this.unref()
211
+ // We send the GOAWAY frame response as no error
212
+ this.destroy()
213
+ util.destroy(this[kSocket], err)
230
214
  }
231
215
 
232
216
  // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
@@ -239,7 +223,7 @@ function writeH2 (client, request) {
239
223
  const { body, method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request
240
224
 
241
225
  if (upgrade) {
242
- errorRequest(client, request, new Error('Upgrade not supported for H2'))
226
+ util.errorRequest(client, request, new Error('Upgrade not supported for H2'))
243
227
  return false
244
228
  }
245
229
 
@@ -292,10 +276,10 @@ function writeH2 (client, request) {
292
276
  }
293
277
  }
294
278
 
295
- errorRequest(client, request, err)
279
+ util.errorRequest(client, request, err)
296
280
  })
297
281
  } catch (err) {
298
- errorRequest(client, request, err)
282
+ util.errorRequest(client, request, err)
299
283
  }
300
284
 
301
285
  if (method === 'CONNECT') {
@@ -370,7 +354,7 @@ function writeH2 (client, request) {
370
354
  // A user agent may send a Content-Length header with 0 value, this should be allowed.
371
355
  if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) {
372
356
  if (client[kStrictContentLength]) {
373
- errorRequest(client, request, new RequestContentLengthMismatchError())
357
+ util.errorRequest(client, request, new RequestContentLengthMismatchError())
374
358
  return false
375
359
  }
376
360
 
@@ -412,7 +396,7 @@ function writeH2 (client, request) {
412
396
  // as there's no value to keep it open.
413
397
  if (request.aborted || request.completed) {
414
398
  const err = new RequestAbortedError()
415
- errorRequest(client, request, err)
399
+ util.errorRequest(client, request, err)
416
400
  util.destroy(stream, err)
417
401
  return
418
402
  }
@@ -446,13 +430,12 @@ function writeH2 (client, request) {
446
430
  }
447
431
 
448
432
  const err = new InformationalError('HTTP/2: stream half-closed (remote)')
449
- errorRequest(client, request, err)
433
+ util.errorRequest(client, request, err)
450
434
  util.destroy(stream, err)
451
435
  })
452
436
 
453
437
  stream.once('close', () => {
454
438
  session[kOpenStreams] -= 1
455
- // TODO(HTTP/2): unref only if current streams count is 0
456
439
  if (session[kOpenStreams] === 0) {
457
440
  session.unref()
458
441
  }
@@ -461,13 +444,14 @@ function writeH2 (client, request) {
461
444
  stream.once('error', function (err) {
462
445
  if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) {
463
446
  session[kOpenStreams] -= 1
447
+ util.errorRequest(client, request, err)
464
448
  util.destroy(stream, err)
465
449
  }
466
450
  })
467
451
 
468
452
  stream.once('frameError', (type, code) => {
469
453
  const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
470
- errorRequest(client, request, err)
454
+ util.errorRequest(client, request, err)
471
455
 
472
456
  if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) {
473
457
  session[kOpenStreams] -= 1
@@ -338,7 +338,7 @@ class Client extends DispatcherBase {
338
338
  const requests = this[kQueue].splice(this[kPendingIdx])
339
339
  for (let i = 0; i < requests.length; i++) {
340
340
  const request = requests[i]
341
- errorRequest(this, request, err)
341
+ util.errorRequest(this, request, err)
342
342
  }
343
343
 
344
344
  const callback = () => {
@@ -378,7 +378,7 @@ function onError (client, err) {
378
378
  const requests = client[kQueue].splice(client[kRunningIdx])
379
379
  for (let i = 0; i < requests.length; i++) {
380
380
  const request = requests[i]
381
- errorRequest(client, request, err)
381
+ util.errorRequest(client, request, err)
382
382
  }
383
383
  assert(client[kSize] === 0)
384
384
  }
@@ -502,7 +502,7 @@ async function connect (client) {
502
502
  assert(client[kRunning] === 0)
503
503
  while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
504
504
  const request = client[kQueue][client[kPendingIdx]++]
505
- errorRequest(client, request, err)
505
+ util.errorRequest(client, request, err)
506
506
  }
507
507
  } else {
508
508
  onError(client, err)
@@ -581,7 +581,10 @@ function _resume (client, sync) {
581
581
  }
582
582
 
583
583
  client[kServerName] = request.servername
584
- client[kHTTPContext]?.destroy(new InformationalError('servername changed'))
584
+ client[kHTTPContext]?.destroy(new InformationalError('servername changed'), () => {
585
+ client[kHTTPContext] = null
586
+ resume(client)
587
+ })
585
588
  }
586
589
 
587
590
  if (client[kConnecting]) {
@@ -609,13 +612,4 @@ function _resume (client, sync) {
609
612
  }
610
613
  }
611
614
 
612
- function errorRequest (client, request, err) {
613
- try {
614
- request.onError(err)
615
- assert(request.aborted)
616
- } catch (err) {
617
- client.emit('error', err)
618
- }
619
- }
620
-
621
615
  module.exports = Client
@@ -64,19 +64,19 @@ class ProxyAgent extends DispatcherBase {
64
64
  this[kAgent] = new Agent({
65
65
  ...opts,
66
66
  connect: async (opts, callback) => {
67
- let requestedHost = opts.host
67
+ let requestedPath = opts.host
68
68
  if (!opts.port) {
69
- requestedHost += `:${defaultProtocolPort(opts.protocol)}`
69
+ requestedPath += `:${defaultProtocolPort(opts.protocol)}`
70
70
  }
71
71
  try {
72
72
  const { socket, statusCode } = await this[kClient].connect({
73
73
  origin,
74
74
  port,
75
- path: requestedHost,
75
+ path: requestedPath,
76
76
  signal: opts.signal,
77
77
  headers: {
78
78
  ...this[kProxyHeaders],
79
- host: requestedHost
79
+ host: opts.host
80
80
  },
81
81
  servername: this[kProxyTls]?.servername || proxyHostname
82
82
  })
@@ -108,16 +108,18 @@ class ProxyAgent extends DispatcherBase {
108
108
  }
109
109
 
110
110
  dispatch (opts, handler) {
111
- const { host } = new URL(opts.origin)
112
111
  const headers = buildHeaders(opts.headers)
113
112
  throwIfProxyAuthIsSent(headers)
113
+
114
+ if (headers && !('host' in headers) && !('Host' in headers)) {
115
+ const { host } = new URL(opts.origin)
116
+ headers.host = host
117
+ }
118
+
114
119
  return this[kAgent].dispatch(
115
120
  {
116
121
  ...opts,
117
- headers: {
118
- ...headers,
119
- host
120
- }
122
+ headers
121
123
  },
122
124
  handler
123
125
  )
@@ -90,7 +90,7 @@ class MockInterceptor {
90
90
  this[kContentLength] = false
91
91
  }
92
92
 
93
- createMockScopeDispatchData (statusCode, data, responseOptions = {}) {
93
+ createMockScopeDispatchData ({ statusCode, data, responseOptions }) {
94
94
  const responseData = getResponseData(data)
95
95
  const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {}
96
96
  const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers }
@@ -99,14 +99,11 @@ class MockInterceptor {
99
99
  return { statusCode, data, headers, trailers }
100
100
  }
101
101
 
102
- validateReplyParameters (statusCode, data, responseOptions) {
103
- if (typeof statusCode === 'undefined') {
102
+ validateReplyParameters (replyParameters) {
103
+ if (typeof replyParameters.statusCode === 'undefined') {
104
104
  throw new InvalidArgumentError('statusCode must be defined')
105
105
  }
106
- if (typeof data === 'undefined') {
107
- throw new InvalidArgumentError('data must be defined')
108
- }
109
- if (typeof responseOptions !== 'object' || responseOptions === null) {
106
+ if (typeof replyParameters.responseOptions !== 'object' || replyParameters.responseOptions === null) {
110
107
  throw new InvalidArgumentError('responseOptions must be an object')
111
108
  }
112
109
  }
@@ -114,28 +111,28 @@ class MockInterceptor {
114
111
  /**
115
112
  * Mock an undici request with a defined reply.
116
113
  */
117
- reply (replyData) {
114
+ reply (replyOptionsCallbackOrStatusCode) {
118
115
  // Values of reply aren't available right now as they
119
116
  // can only be available when the reply callback is invoked.
120
- if (typeof replyData === 'function') {
117
+ if (typeof replyOptionsCallbackOrStatusCode === 'function') {
121
118
  // We'll first wrap the provided callback in another function,
122
119
  // this function will properly resolve the data from the callback
123
120
  // when invoked.
124
121
  const wrappedDefaultsCallback = (opts) => {
125
122
  // Our reply options callback contains the parameter for statusCode, data and options.
126
- const resolvedData = replyData(opts)
123
+ const resolvedData = replyOptionsCallbackOrStatusCode(opts)
127
124
 
128
125
  // Check if it is in the right format
129
- if (typeof resolvedData !== 'object') {
126
+ if (typeof resolvedData !== 'object' || resolvedData === null) {
130
127
  throw new InvalidArgumentError('reply options callback must return an object')
131
128
  }
132
129
 
133
- const { statusCode, data = '', responseOptions = {} } = resolvedData
134
- this.validateReplyParameters(statusCode, data, responseOptions)
130
+ const replyParameters = { data: '', responseOptions: {}, ...resolvedData }
131
+ this.validateReplyParameters(replyParameters)
135
132
  // Since the values can be obtained immediately we return them
136
133
  // from this higher order function that will be resolved later.
137
134
  return {
138
- ...this.createMockScopeDispatchData(statusCode, data, responseOptions)
135
+ ...this.createMockScopeDispatchData(replyParameters)
139
136
  }
140
137
  }
141
138
 
@@ -148,11 +145,15 @@ class MockInterceptor {
148
145
  // we should have 1-3 parameters. So we spread the arguments of
149
146
  // this function to obtain the parameters, since replyData will always
150
147
  // just be the statusCode.
151
- const [statusCode, data = '', responseOptions = {}] = [...arguments]
152
- this.validateReplyParameters(statusCode, data, responseOptions)
148
+ const replyParameters = {
149
+ statusCode: replyOptionsCallbackOrStatusCode,
150
+ data: arguments[1] === undefined ? '' : arguments[1],
151
+ responseOptions: arguments[2] === undefined ? {} : arguments[2]
152
+ }
153
+ this.validateReplyParameters(replyParameters)
153
154
 
154
155
  // Send in-already provided data like usual
155
- const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions)
156
+ const dispatchData = this.createMockScopeDispatchData(replyParameters)
156
157
  const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)
157
158
  return new MockScope(newMockDispatch)
158
159
  }
@@ -8,7 +8,7 @@ const {
8
8
  kOrigin,
9
9
  kGetNetConnect
10
10
  } = require('./mock-symbols')
11
- const { buildURL, nop } = require('../core/util')
11
+ const { buildURL } = require('../core/util')
12
12
  const { STATUS_CODES } = require('node:http')
13
13
  const {
14
14
  types: {
@@ -285,10 +285,10 @@ function mockDispatch (opts, handler) {
285
285
  const responseHeaders = generateKeyValues(headers)
286
286
  const responseTrailers = generateKeyValues(trailers)
287
287
 
288
- handler.abort = nop
289
- handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode))
290
- handler.onData(Buffer.from(responseData))
291
- handler.onComplete(responseTrailers)
288
+ handler.onConnect?.(err => handler.onError(err), null)
289
+ handler.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode))
290
+ handler.onData?.(Buffer.from(responseData))
291
+ handler.onComplete?.(responseTrailers)
292
292
  deleteMockDispatch(mockDispatches, key)
293
293
  }
294
294
 
@@ -14,8 +14,8 @@ const badPorts = [
14
14
  '87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137',
15
15
  '139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532',
16
16
  '540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723',
17
- '2049', '3659', '4045', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697',
18
- '10080'
17
+ '2049', '3659', '4045', '4190', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6679',
18
+ '6697', '10080'
19
19
  ]
20
20
 
21
21
  const badPortsSet = new Set(badPorts)
@@ -4,7 +4,7 @@ const { Writable } = require('node:stream')
4
4
  const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants')
5
5
  const { kReadyState, kSentClose, kResponse, kReceivedClose } = require('./symbols')
6
6
  const { channels } = require('../../core/diagnostics')
7
- const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived } = require('./util')
7
+ const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived, utf8Decode } = require('./util')
8
8
  const { WebsocketFrameSend } = require('./frame')
9
9
 
10
10
  // This code was influenced by ws released under the MIT license.
@@ -314,8 +314,7 @@ class ByteParser extends Writable {
314
314
  }
315
315
 
316
316
  try {
317
- // TODO: optimize this
318
- reason = new TextDecoder('utf-8', { fatal: true }).decode(reason)
317
+ reason = utf8Decode(reason)
319
318
  } catch {
320
319
  return null
321
320
  }
@@ -3,6 +3,7 @@
3
3
  const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require('./symbols')
4
4
  const { states, opcodes } = require('./constants')
5
5
  const { MessageEvent, ErrorEvent } = require('./events')
6
+ const { isUtf8 } = require('node:buffer')
6
7
 
7
8
  /* globals Blob */
8
9
 
@@ -87,7 +88,7 @@ function websocketMessageReceived (ws, type, data) {
87
88
  // -> type indicates that the data is Text
88
89
  // a new DOMString containing data
89
90
  try {
90
- dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data)
91
+ dataForEvent = utf8Decode(data)
91
92
  } catch {
92
93
  failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.')
93
94
  return
@@ -200,6 +201,33 @@ function failWebsocketConnection (ws, reason) {
200
201
  }
201
202
  }
202
203
 
204
+ // https://nodejs.org/api/intl.html#detecting-internationalization-support
205
+ const hasIntl = typeof process.versions.icu === 'string'
206
+ const fatalDecoder = hasIntl ? new TextDecoder('utf-8', { fatal: true }) : undefined
207
+
208
+ /**
209
+ * Converts a Buffer to utf-8, even on platforms without icu.
210
+ * @param {Buffer} buffer
211
+ */
212
+ function utf8Decode (buffer) {
213
+ if (hasIntl) {
214
+ return fatalDecoder.decode(buffer)
215
+ } else {
216
+ if (!isUtf8?.(buffer)) {
217
+ // TODO: remove once node 18 or < node v18.14.0 is dropped
218
+ if (!isUtf8) {
219
+ process.emitWarning('ICU is not supported and no fallback exists. Please upgrade to at least Node v18.14.0.', {
220
+ code: 'UNDICI-WS-NO-ICU'
221
+ })
222
+ }
223
+
224
+ throw new TypeError('Invalid utf-8 received.')
225
+ }
226
+
227
+ return buffer.toString('utf-8')
228
+ }
229
+ }
230
+
203
231
  module.exports = {
204
232
  isConnecting,
205
233
  isEstablished,
@@ -209,5 +237,6 @@ module.exports = {
209
237
  isValidSubprotocol,
210
238
  isValidStatusCode,
211
239
  failWebsocketConnection,
212
- websocketMessageReceived
240
+ websocketMessageReceived,
241
+ utf8Decode
213
242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.11.1",
3
+ "version": "6.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": {
@@ -68,20 +68,25 @@
68
68
  "lint": "standard | snazzy",
69
69
  "lint:fix": "standard --fix | snazzy",
70
70
  "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
71
- "test:javascript": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:jest",
72
- "test:javascript:withoutintl": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:fetch:nobuild && npm run test:cookies && npm run test:eventsource:nobuild && npm run test:wpt:withoutintl && npm run test:node-test",
71
+ "test:javascript": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:jest",
72
+ "test:javascript:withoutintl": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:fetch:nobuild && npm run test:cache && npm run test:interceptors && npm run test:cookies && npm run test:eventsource:nobuild && npm run test:wpt:withoutintl && npm run test:node-test",
73
+ "test:busboy": "borp -p \"test/busboy/*.js\"",
74
+ "test:cache": "borp -p \"test/cache/*.js\"",
73
75
  "test:cookies": "borp -p \"test/cookie/*.js\"",
74
- "test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"",
75
76
  "test:eventsource": "npm run build:node && npm run test:eventsource:nobuild",
76
77
  "test:eventsource:nobuild": "borp --expose-gc -p \"test/eventsource/*.js\"",
78
+ "test:fuzzing": "node test/fuzzing/fuzzing.test.js",
77
79
  "test:fetch": "npm run build:node && npm run test:fetch:nobuild",
78
- "test:fetch:nobuild": "borp --expose-gc -p \"test/fetch/*.js\" && borp -p \"test/webidl/*.js\" && borp -p \"test/busboy/*.js\"",
80
+ "test:fetch:nobuild": "borp --expose-gc -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
81
+ "test:interceptors": "borp -p \"test/interceptors/*.js\"",
79
82
  "test:jest": "cross-env NODE_V8_COVERAGE= jest",
80
83
  "test:unit": "borp --expose-gc -p \"test/*.js\"",
84
+ "test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"",
81
85
  "test:node-test": "borp -p \"test/node-test/**/*.js\"",
82
86
  "test:tdd": "borp --expose-gc -p \"test/*.js\"",
83
87
  "test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w",
84
88
  "test:typescript": "tsd && tsc --skipLibCheck test/imports/undici-import.ts",
89
+ "test:webidl": "borp -p \"test/webidl/*.js\"",
85
90
  "test:websocket": "borp -p \"test/websocket/*.js\"",
86
91
  "test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
87
92
  "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
@@ -92,8 +97,7 @@
92
97
  "coverage:report:ci": "c8 report",
93
98
  "bench": "echo \"Error: Benchmarks have been moved to '/benchmarks'\" && exit 1",
94
99
  "serve:website": "echo \"Error: Documentation has been moved to '/docs'\" && exit 1",
95
- "prepare": "husky install && node ./scripts/platform-shell.js",
96
- "fuzz": "jsfuzz test/fuzzing/fuzz.js corpus"
100
+ "prepare": "husky install && node ./scripts/platform-shell.js"
97
101
  },
98
102
  "devDependencies": {
99
103
  "@matteo.collina/tspl": "^0.1.1",
@@ -104,13 +108,13 @@
104
108
  "c8": "^9.1.0",
105
109
  "cross-env": "^7.0.3",
106
110
  "dns-packet": "^5.4.0",
111
+ "fast-check": "^3.17.1",
107
112
  "form-data": "^4.0.0",
108
113
  "formdata-node": "^6.0.3",
109
114
  "https-pem": "^3.0.0",
110
115
  "husky": "^9.0.7",
111
116
  "jest": "^29.0.2",
112
117
  "jsdom": "^24.0.0",
113
- "jsfuzz": "^1.0.15",
114
118
  "node-forge": "^1.3.1",
115
119
  "pre-commit": "^1.2.2",
116
120
  "proxy": "^2.1.1",
@@ -225,7 +225,7 @@ declare namespace Dispatcher {
225
225
  /** Invoked when response is received, before headers have been read. **/
226
226
  onResponseStarted?(): void;
227
227
  /** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */
228
- onHeaders?(statusCode: number, headers: Buffer[] | string[] | null, resume: () => void, statusText: string): boolean;
228
+ onHeaders?(statusCode: number, headers: Buffer[], resume: () => void, statusText: string): boolean;
229
229
  /** Invoked when response payload data is received. */
230
230
  onData?(chunk: Buffer): boolean;
231
231
  /** Invoked when response payload and trailers have been received and the request has completed. */
package/types/fetch.d.ts CHANGED
@@ -163,7 +163,7 @@ export declare class Request extends BodyMixin {
163
163
  readonly method: string
164
164
  readonly mode: RequestMode
165
165
  readonly redirect: RequestRedirect
166
- readonly referrerPolicy: string
166
+ readonly referrerPolicy: ReferrerPolicy
167
167
  readonly url: string
168
168
 
169
169
  readonly keepalive: boolean