undici 6.6.1 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/{README.md → docs/README.md} +19 -15
  2. package/docs/{api → docs/api}/Dispatcher.md +39 -3
  3. package/docs/docs/api/Fetch.md +57 -0
  4. package/docs/{api → docs/api}/ProxyAgent.md +3 -1
  5. package/docs/docs/api/RetryAgent.md +45 -0
  6. package/docs/{api → docs/api}/RetryHandler.md +1 -1
  7. package/docs/{api → docs/api}/api-lifecycle.md +33 -4
  8. package/docs/{best-practices → docs/best-practices}/proxy.md +6 -6
  9. package/index-fetch.js +11 -7
  10. package/index.js +31 -25
  11. package/lib/core/request.js +72 -135
  12. package/lib/core/symbols.js +6 -5
  13. package/lib/core/tree.js +46 -26
  14. package/lib/core/util.js +41 -20
  15. package/lib/{agent.js → dispatcher/agent.js} +4 -4
  16. package/lib/{balanced-pool.js → dispatcher/balanced-pool.js} +3 -3
  17. package/lib/dispatcher/client-h1.js +1339 -0
  18. package/lib/dispatcher/client-h2.js +639 -0
  19. package/lib/dispatcher/client.js +611 -0
  20. package/lib/{dispatcher-base.js → dispatcher/dispatcher-base.js} +2 -2
  21. package/lib/{pool-base.js → dispatcher/pool-base.js} +3 -3
  22. package/lib/{pool-stats.js → dispatcher/pool-stats.js} +1 -1
  23. package/lib/{pool.js → dispatcher/pool.js} +4 -4
  24. package/lib/{proxy-agent.js → dispatcher/proxy-agent.js} +29 -35
  25. package/lib/dispatcher/retry-agent.js +35 -0
  26. package/lib/global.js +1 -1
  27. package/lib/handler/{RetryHandler.js → retry-handler.js} +2 -2
  28. package/lib/interceptor/{redirectInterceptor.js → redirect-interceptor.js} +1 -1
  29. package/lib/mock/mock-agent.js +2 -2
  30. package/lib/mock/mock-client.js +1 -1
  31. package/lib/mock/mock-interceptor.js +2 -2
  32. package/lib/mock/mock-pool.js +1 -1
  33. package/lib/mock/mock-utils.js +6 -4
  34. package/lib/{cache → web/cache}/cache.js +2 -4
  35. package/lib/{cache → web/cache}/cachestorage.js +1 -1
  36. package/lib/web/cache/symbols.js +5 -0
  37. package/lib/{cache → web/cache}/util.js +5 -9
  38. package/lib/{cookies → web/cookies}/parse.js +1 -1
  39. package/lib/{cookies → web/cookies}/util.js +76 -60
  40. package/lib/{eventsource → web/eventsource}/eventsource.js +2 -6
  41. package/lib/{fetch → web/fetch}/body.js +23 -52
  42. package/lib/{fetch/dataURL.js → web/fetch/data-url.js} +2 -0
  43. package/lib/{compat → web/fetch}/dispatcher-weakref.js +1 -1
  44. package/lib/{fetch → web/fetch}/file.js +2 -2
  45. package/lib/{fetch → web/fetch}/formdata.js +6 -67
  46. package/lib/{fetch → web/fetch}/headers.js +99 -71
  47. package/lib/{fetch → web/fetch}/index.js +40 -31
  48. package/lib/{fetch → web/fetch}/request.js +14 -6
  49. package/lib/{fetch → web/fetch}/response.js +3 -3
  50. package/lib/{fetch → web/fetch}/symbols.js +2 -1
  51. package/lib/{fetch → web/fetch}/util.js +142 -48
  52. package/lib/{fetch → web/fetch}/webidl.js +53 -19
  53. package/lib/{fileapi → web/fileapi}/filereader.js +1 -1
  54. package/lib/{fileapi → web/fileapi}/util.js +1 -1
  55. package/lib/{websocket → web/websocket}/connection.js +20 -10
  56. package/lib/{websocket → web/websocket}/constants.js +7 -0
  57. package/lib/{websocket → web/websocket}/events.js +1 -1
  58. package/lib/{websocket → web/websocket}/frame.js +1 -0
  59. package/lib/{websocket → web/websocket}/receiver.js +9 -16
  60. package/lib/{websocket → web/websocket}/util.js +37 -23
  61. package/lib/{websocket → web/websocket}/websocket.js +21 -9
  62. package/package.json +27 -52
  63. package/types/dispatcher.d.ts +1 -1
  64. package/types/fetch.d.ts +20 -21
  65. package/types/index.d.ts +2 -1
  66. package/types/retry-agent.d.ts +11 -0
  67. package/types/webidl.d.ts +6 -1
  68. package/docs/api/Fetch.md +0 -27
  69. package/docs/assets/lifecycle-diagram.png +0 -0
  70. package/lib/cache/symbols.js +0 -5
  71. package/lib/client.js +0 -2295
  72. package/lib/llhttp/llhttp-wasm.js +0 -3
  73. package/lib/llhttp/llhttp.wasm +0 -0
  74. package/lib/llhttp/llhttp_simd.wasm +0 -0
  75. /package/docs/{api → docs/api}/Agent.md +0 -0
  76. /package/docs/{api → docs/api}/BalancedPool.md +0 -0
  77. /package/docs/{api → docs/api}/CacheStorage.md +0 -0
  78. /package/docs/{api → docs/api}/Client.md +0 -0
  79. /package/docs/{api → docs/api}/Connector.md +0 -0
  80. /package/docs/{api → docs/api}/ContentType.md +0 -0
  81. /package/docs/{api → docs/api}/Cookies.md +0 -0
  82. /package/docs/{api → docs/api}/Debug.md +0 -0
  83. /package/docs/{api → docs/api}/DiagnosticsChannel.md +0 -0
  84. /package/docs/{api → docs/api}/DispatchInterceptor.md +0 -0
  85. /package/docs/{api → docs/api}/Errors.md +0 -0
  86. /package/docs/{api → docs/api}/EventSource.md +0 -0
  87. /package/docs/{api → docs/api}/MockAgent.md +0 -0
  88. /package/docs/{api → docs/api}/MockClient.md +0 -0
  89. /package/docs/{api → docs/api}/MockErrors.md +0 -0
  90. /package/docs/{api → docs/api}/MockPool.md +0 -0
  91. /package/docs/{api → docs/api}/Pool.md +0 -0
  92. /package/docs/{api → docs/api}/PoolStats.md +0 -0
  93. /package/docs/{api → docs/api}/RedirectHandler.md +0 -0
  94. /package/docs/{api → docs/api}/Util.md +0 -0
  95. /package/docs/{api → docs/api}/WebSocket.md +0 -0
  96. /package/docs/{best-practices → docs/best-practices}/client-certificate.md +0 -0
  97. /package/docs/{best-practices → docs/best-practices}/mocking-request.md +0 -0
  98. /package/docs/{best-practices → docs/best-practices}/writing-tests.md +0 -0
  99. /package/lib/{dispatcher.js → dispatcher/dispatcher.js} +0 -0
  100. /package/lib/{node → dispatcher}/fixed-queue.js +0 -0
  101. /package/lib/handler/{DecoratorHandler.js → decorator-handler.js} +0 -0
  102. /package/lib/handler/{RedirectHandler.js → redirect-handler.js} +0 -0
  103. /package/lib/{timers.js → util/timers.js} +0 -0
  104. /package/lib/{cookies → web/cookies}/constants.js +0 -0
  105. /package/lib/{cookies → web/cookies}/index.js +0 -0
  106. /package/lib/{eventsource → web/eventsource}/eventsource-stream.js +0 -0
  107. /package/lib/{eventsource → web/eventsource}/util.js +0 -0
  108. /package/lib/{fetch → web/fetch}/LICENSE +0 -0
  109. /package/lib/{fetch → web/fetch}/constants.js +0 -0
  110. /package/lib/{fetch → web/fetch}/global.js +0 -0
  111. /package/lib/{fileapi → web/fileapi}/encoding.js +0 -0
  112. /package/lib/{fileapi → web/fileapi}/progressevent.js +0 -0
  113. /package/lib/{fileapi → web/fileapi}/symbols.js +0 -0
  114. /package/lib/{websocket → web/websocket}/symbols.js +0 -0
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const { types } = require('node:util')
4
- const { toUSVString } = require('./util')
3
+ const { types, inspect } = require('node:util')
4
+ const { toUSVString } = require('../../core/util')
5
5
 
6
- /** @type {import('../../types/webidl').Webidl} */
6
+ /** @type {import('../../../types/webidl').Webidl} */
7
7
  const webidl = {}
8
8
  webidl.converters = {}
9
9
  webidl.util = {}
@@ -34,10 +34,14 @@ webidl.errors.invalidArgument = function (context) {
34
34
 
35
35
  // https://webidl.spec.whatwg.org/#implements
36
36
  webidl.brandCheck = function (V, I, opts = undefined) {
37
- if (opts?.strict !== false && !(V instanceof I)) {
38
- throw new TypeError('Illegal invocation')
37
+ if (opts?.strict !== false) {
38
+ if (!(V instanceof I)) {
39
+ throw new TypeError('Illegal invocation')
40
+ }
39
41
  } else {
40
- return V?.[Symbol.toStringTag] === I.prototype[Symbol.toStringTag]
42
+ if (V?.[Symbol.toStringTag] !== I.prototype[Symbol.toStringTag]) {
43
+ throw new TypeError('Illegal invocation')
44
+ }
41
45
  }
42
46
  }
43
47
 
@@ -132,7 +136,7 @@ webidl.util.ConvertToInt = function (V, bitLength, signedness, opts = {}) {
132
136
  ) {
133
137
  throw webidl.errors.exception({
134
138
  header: 'Integer conversion',
135
- message: `Could not convert ${V} to an integer.`
139
+ message: `Could not convert ${webidl.util.Stringify(V)} to an integer.`
136
140
  })
137
141
  }
138
142
 
@@ -212,9 +216,24 @@ webidl.util.IntegerPart = function (n) {
212
216
  return r
213
217
  }
214
218
 
219
+ webidl.util.Stringify = function (V) {
220
+ const type = webidl.util.Type(V)
221
+
222
+ switch (type) {
223
+ case 'Symbol':
224
+ return `Symbol(${V.description})`
225
+ case 'Object':
226
+ return inspect(V)
227
+ case 'String':
228
+ return `"${V}"`
229
+ default:
230
+ return `${V}`
231
+ }
232
+ }
233
+
215
234
  // https://webidl.spec.whatwg.org/#es-sequence
216
235
  webidl.sequenceConverter = function (converter) {
217
- return (V) => {
236
+ return (V, Iterable) => {
218
237
  // 1. If Type(V) is not Object, throw a TypeError.
219
238
  if (webidl.util.Type(V) !== 'Object') {
220
239
  throw webidl.errors.exception({
@@ -225,7 +244,7 @@ webidl.sequenceConverter = function (converter) {
225
244
 
226
245
  // 2. Let method be ? GetMethod(V, @@iterator).
227
246
  /** @type {Generator} */
228
- const method = V?.[Symbol.iterator]?.()
247
+ const method = typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.()
229
248
  const seq = []
230
249
 
231
250
  // 3. If method is undefined, throw a TypeError.
@@ -269,8 +288,8 @@ webidl.recordConverter = function (keyConverter, valueConverter) {
269
288
  const result = {}
270
289
 
271
290
  if (!types.isProxy(O)) {
272
- // Object.keys only returns enumerable properties
273
- const keys = Object.keys(O)
291
+ // 1. Let desc be ? O.[[GetOwnProperty]](key).
292
+ const keys = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]
274
293
 
275
294
  for (const key of keys) {
276
295
  // 1. Let typedKey be key converted to an IDL value of type K.
@@ -320,7 +339,7 @@ webidl.interfaceConverter = function (i) {
320
339
  if (opts.strict !== false && !(V instanceof i)) {
321
340
  throw webidl.errors.exception({
322
341
  header: i.name,
323
- message: `Expected ${V} to be an instance of ${i.name}.`
342
+ message: `Expected ${webidl.util.Stringify(V)} to be an instance of ${i.name}.`
324
343
  })
325
344
  }
326
345
 
@@ -511,8 +530,8 @@ webidl.converters.ArrayBuffer = function (V, opts = {}) {
511
530
  !types.isAnyArrayBuffer(V)
512
531
  ) {
513
532
  throw webidl.errors.conversionFailed({
514
- prefix: `${V}`,
515
- argument: `${V}`,
533
+ prefix: webidl.util.Stringify(V),
534
+ argument: webidl.util.Stringify(V),
516
535
  types: ['ArrayBuffer']
517
536
  })
518
537
  }
@@ -532,7 +551,12 @@ webidl.converters.ArrayBuffer = function (V, opts = {}) {
532
551
  // with the [AllowResizable] extended attribute, and
533
552
  // IsResizableArrayBuffer(V) is true, then throw a
534
553
  // TypeError.
535
- // Note: resizable ArrayBuffers are currently a proposal.
554
+ if (V.resizable || V.growable) {
555
+ throw webidl.errors.exception({
556
+ header: 'ArrayBuffer',
557
+ message: 'Received a resizable ArrayBuffer.'
558
+ })
559
+ }
536
560
 
537
561
  // 4. Return the IDL ArrayBuffer value that is a
538
562
  // reference to the same object as V.
@@ -552,7 +576,7 @@ webidl.converters.TypedArray = function (V, T, opts = {}) {
552
576
  ) {
553
577
  throw webidl.errors.conversionFailed({
554
578
  prefix: `${T.name}`,
555
- argument: `${V}`,
579
+ argument: webidl.util.Stringify(V),
556
580
  types: [T.name]
557
581
  })
558
582
  }
@@ -572,7 +596,12 @@ webidl.converters.TypedArray = function (V, T, opts = {}) {
572
596
  // with the [AllowResizable] extended attribute, and
573
597
  // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
574
598
  // true, then throw a TypeError.
575
- // Note: resizable array buffers are currently a proposal
599
+ if (V.buffer.resizable || V.buffer.growable) {
600
+ throw webidl.errors.exception({
601
+ header: 'ArrayBuffer',
602
+ message: 'Received a resizable ArrayBuffer.'
603
+ })
604
+ }
576
605
 
577
606
  // 5. Return the IDL value of type T that is a reference
578
607
  // to the same object as V.
@@ -604,7 +633,12 @@ webidl.converters.DataView = function (V, opts = {}) {
604
633
  // with the [AllowResizable] extended attribute, and
605
634
  // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
606
635
  // true, then throw a TypeError.
607
- // Note: resizable ArrayBuffers are currently a proposal
636
+ if (V.buffer.resizable || V.buffer.growable) {
637
+ throw webidl.errors.exception({
638
+ header: 'ArrayBuffer',
639
+ message: 'Received a resizable ArrayBuffer.'
640
+ })
641
+ }
608
642
 
609
643
  // 4. Return the IDL DataView value that is a reference
610
644
  // to the same object as V.
@@ -625,7 +659,7 @@ webidl.converters.BufferSource = function (V, opts = {}) {
625
659
  return webidl.converters.DataView(V, opts, { ...opts, allowShared: false })
626
660
  }
627
661
 
628
- throw new TypeError(`Could not convert ${V} to a BufferSource.`)
662
+ throw new TypeError(`Could not convert ${webidl.util.Stringify(V)} to a BufferSource.`)
629
663
  }
630
664
 
631
665
  webidl.converters['sequence<ByteString>'] = webidl.sequenceConverter(
@@ -13,7 +13,7 @@ const {
13
13
  kAborted
14
14
  } = require('./symbols')
15
15
  const { webidl } = require('../fetch/webidl')
16
- const { kEnumerableProperty } = require('../core/util')
16
+ const { kEnumerableProperty } = require('../../core/util')
17
17
 
18
18
  class FileReader extends EventTarget {
19
19
  constructor () {
@@ -9,7 +9,7 @@ const {
9
9
  } = require('./symbols')
10
10
  const { ProgressEvent } = require('./progressevent')
11
11
  const { getEncoding } = require('./encoding')
12
- const { serializeAMimeType, parseMIMEType } = require('../fetch/dataURL')
12
+ const { serializeAMimeType, parseMIMEType } = require('../fetch/data-url')
13
13
  const { types } = require('node:util')
14
14
  const { StringDecoder } = require('string_decoder')
15
15
  const { btoa } = require('node:buffer')
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { uid, states } = require('./constants')
3
+ const { uid, states, sentCloseFrameState } = require('./constants')
4
4
  const {
5
5
  kReadyState,
6
6
  kSentClose,
@@ -8,18 +8,19 @@ const {
8
8
  kReceivedClose
9
9
  } = require('./symbols')
10
10
  const { fireEvent, failWebsocketConnection } = require('./util')
11
- const { channels } = require('../core/diagnostics')
11
+ const { channels } = require('../../core/diagnostics')
12
12
  const { CloseEvent } = require('./events')
13
13
  const { makeRequest } = require('../fetch/request')
14
14
  const { fetching } = require('../fetch/index')
15
15
  const { Headers } = require('../fetch/headers')
16
- const { getGlobalDispatcher } = require('../global')
17
- const { kHeadersList } = require('../core/symbols')
16
+ const { getDecodeSplit } = require('../fetch/util')
17
+ const { kHeadersList } = require('../../core/symbols')
18
18
 
19
19
  /** @type {import('crypto')} */
20
20
  let crypto
21
21
  try {
22
22
  crypto = require('node:crypto')
23
+ /* c8 ignore next 3 */
23
24
  } catch {
24
25
 
25
26
  }
@@ -100,7 +101,7 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish, options)
100
101
  const controller = fetching({
101
102
  request,
102
103
  useParallelQueue: true,
103
- dispatcher: options.dispatcher ?? getGlobalDispatcher(),
104
+ dispatcher: options.dispatcher,
104
105
  processResponse (response) {
105
106
  // 1. If response is a network error or its status is not 101,
106
107
  // fail the WebSocket connection.
@@ -177,9 +178,18 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish, options)
177
178
  // the WebSocket Connection_.
178
179
  const secProtocol = response.headersList.get('Sec-WebSocket-Protocol')
179
180
 
180
- if (secProtocol !== null && secProtocol !== request.headersList.get('Sec-WebSocket-Protocol')) {
181
- failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
182
- return
181
+ if (secProtocol !== null) {
182
+ const requestProtocols = getDecodeSplit('sec-websocket-protocol', request.headersList)
183
+
184
+ // The client can request that the server use a specific subprotocol by
185
+ // including the |Sec-WebSocket-Protocol| field in its handshake. If it
186
+ // is specified, the server needs to include the same field and one of
187
+ // the selected subprotocol values in its response for the connection to
188
+ // be established.
189
+ if (!requestProtocols.includes(secProtocol)) {
190
+ failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
191
+ return
192
+ }
183
193
  }
184
194
 
185
195
  response.socket.on('data', onSocketData)
@@ -220,7 +230,7 @@ function onSocketClose () {
220
230
  // If the TCP connection was closed after the
221
231
  // WebSocket closing handshake was completed, the WebSocket connection
222
232
  // is said to have been closed _cleanly_.
223
- const wasClean = ws[kSentClose] && ws[kReceivedClose]
233
+ const wasClean = ws[kSentClose] === sentCloseFrameState.SENT && ws[kReceivedClose]
224
234
 
225
235
  let code = 1005
226
236
  let reason = ''
@@ -230,7 +240,7 @@ function onSocketClose () {
230
240
  if (result) {
231
241
  code = result.code ?? 1005
232
242
  reason = result.reason
233
- } else if (!ws[kSentClose]) {
243
+ } else if (ws[kSentClose] !== sentCloseFrameState.SENT) {
234
244
  // If _The WebSocket
235
245
  // Connection is Closed_ and no Close control frame was received by the
236
246
  // endpoint (such as could occur if the underlying transport connection
@@ -20,6 +20,12 @@ const states = {
20
20
  CLOSED: 3
21
21
  }
22
22
 
23
+ const sentCloseFrameState = {
24
+ NOT_SENT: 0,
25
+ PROCESSING: 1,
26
+ SENT: 2
27
+ }
28
+
23
29
  const opcodes = {
24
30
  CONTINUATION: 0x0,
25
31
  TEXT: 0x1,
@@ -42,6 +48,7 @@ const emptyBuffer = Buffer.allocUnsafe(0)
42
48
 
43
49
  module.exports = {
44
50
  uid,
51
+ sentCloseFrameState,
45
52
  staticPropertyDescriptors,
46
53
  states,
47
54
  opcodes,
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { webidl } = require('../fetch/webidl')
4
- const { kEnumerableProperty } = require('../core/util')
4
+ const { kEnumerableProperty } = require('../../core/util')
5
5
  const { MessagePort } = require('node:worker_threads')
6
6
 
7
7
  /**
@@ -6,6 +6,7 @@ const { maxUnsigned16Bit } = require('./constants')
6
6
  let crypto
7
7
  try {
8
8
  crypto = require('node:crypto')
9
+ /* c8 ignore next 3 */
9
10
  } catch {
10
11
 
11
12
  }
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const { Writable } = require('node:stream')
4
- const { parserStates, opcodes, states, emptyBuffer } = require('./constants')
4
+ const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants')
5
5
  const { kReadyState, kSentClose, kResponse, kReceivedClose } = require('./symbols')
6
- const { channels } = require('../core/diagnostics')
6
+ const { channels } = require('../../core/diagnostics')
7
7
  const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived } = require('./util')
8
8
  const { WebsocketFrameSend } = require('./frame')
9
9
 
@@ -12,6 +12,8 @@ const { WebsocketFrameSend } = require('./frame')
12
12
  // Copyright (c) 2013 Arnout Kazemier and contributors
13
13
  // Copyright (c) 2016 Luigi Pinca and contributors
14
14
 
15
+ const textDecoder = new TextDecoder('utf-8', { fatal: true })
16
+
15
17
  class ByteParser extends Writable {
16
18
  #buffers = []
17
19
  #byteOffset = 0
@@ -100,9 +102,9 @@ class ByteParser extends Writable {
100
102
 
101
103
  const body = this.consume(payloadLength)
102
104
 
103
- this.#info.closeInfo = this.parseCloseBody(false, body)
105
+ this.#info.closeInfo = this.parseCloseBody(body)
104
106
 
105
- if (!this.ws[kSentClose]) {
107
+ if (this.ws[kSentClose] !== sentCloseFrameState.SENT) {
106
108
  // If an endpoint receives a Close frame and did not previously send a
107
109
  // Close frame, the endpoint MUST send a Close frame in response. (When
108
110
  // sending a Close frame in response, the endpoint typically echos the
@@ -118,7 +120,7 @@ class ByteParser extends Writable {
118
120
  closeFrame.createFrame(opcodes.CLOSE),
119
121
  (err) => {
120
122
  if (!err) {
121
- this.ws[kSentClose] = true
123
+ this.ws[kSentClose] = sentCloseFrameState.SENT
122
124
  }
123
125
  }
124
126
  )
@@ -288,7 +290,7 @@ class ByteParser extends Writable {
288
290
  return buffer
289
291
  }
290
292
 
291
- parseCloseBody (onlyCode, data) {
293
+ parseCloseBody (data) {
292
294
  // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
293
295
  /** @type {number|undefined} */
294
296
  let code
@@ -300,14 +302,6 @@ class ByteParser extends Writable {
300
302
  code = data.readUInt16BE(0)
301
303
  }
302
304
 
303
- if (onlyCode) {
304
- if (!isValidStatusCode(code)) {
305
- return null
306
- }
307
-
308
- return { code }
309
- }
310
-
311
305
  // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6
312
306
  /** @type {Buffer} */
313
307
  let reason = data.subarray(2)
@@ -322,8 +316,7 @@ class ByteParser extends Writable {
322
316
  }
323
317
 
324
318
  try {
325
- // TODO: optimize this
326
- reason = new TextDecoder('utf-8', { fatal: true }).decode(reason)
319
+ reason = textDecoder.decode(reason)
327
320
  } catch {
328
321
  return null
329
322
  }
@@ -8,6 +8,17 @@ const { MessageEvent, ErrorEvent } = require('./events')
8
8
 
9
9
  /**
10
10
  * @param {import('./websocket').WebSocket} ws
11
+ * @returns {boolean}
12
+ */
13
+ function isConnecting (ws) {
14
+ // If the WebSocket connection is not yet established, and the connection
15
+ // is not yet closed, then the WebSocket connection is in the CONNECTING state.
16
+ return ws[kReadyState] === states.CONNECTING
17
+ }
18
+
19
+ /**
20
+ * @param {import('./websocket').WebSocket} ws
21
+ * @returns {boolean}
11
22
  */
12
23
  function isEstablished (ws) {
13
24
  // If the server's response is validated as provided for above, it is
@@ -18,6 +29,7 @@ function isEstablished (ws) {
18
29
 
19
30
  /**
20
31
  * @param {import('./websocket').WebSocket} ws
32
+ * @returns {boolean}
21
33
  */
22
34
  function isClosing (ws) {
23
35
  // Upon either sending or receiving a Close control frame, it is said
@@ -28,6 +40,7 @@ function isClosing (ws) {
28
40
 
29
41
  /**
30
42
  * @param {import('./websocket').WebSocket} ws
43
+ * @returns {boolean}
31
44
  */
32
45
  function isClosed (ws) {
33
46
  return ws[kReadyState] === states.CLOSED
@@ -55,6 +68,8 @@ function fireEvent (e, target, eventConstructor = Event, eventInitDict = {}) {
55
68
  target.dispatchEvent(event)
56
69
  }
57
70
 
71
+ const textDecoder = new TextDecoder('utf-8', { fatal: true })
72
+
58
73
  /**
59
74
  * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
60
75
  * @param {import('./websocket').WebSocket} ws
@@ -74,7 +89,7 @@ function websocketMessageReceived (ws, type, data) {
74
89
  // -> type indicates that the data is Text
75
90
  // a new DOMString containing data
76
91
  try {
77
- dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data)
92
+ dataForEvent = textDecoder.decode(data)
78
93
  } catch {
79
94
  failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.')
80
95
  return
@@ -119,31 +134,29 @@ function isValidSubprotocol (protocol) {
119
134
  return false
120
135
  }
121
136
 
122
- for (const char of protocol) {
123
- const code = char.charCodeAt(0)
137
+ for (let i = 0; i < protocol.length; ++i) {
138
+ const code = protocol.charCodeAt(i)
124
139
 
125
140
  if (
126
- code < 0x21 ||
141
+ code < 0x21 || // CTL, contains SP (0x20) and HT (0x09)
127
142
  code > 0x7E ||
128
- char === '(' ||
129
- char === ')' ||
130
- char === '<' ||
131
- char === '>' ||
132
- char === '@' ||
133
- char === ',' ||
134
- char === ';' ||
135
- char === ':' ||
136
- char === '\\' ||
137
- char === '"' ||
138
- char === '/' ||
139
- char === '[' ||
140
- char === ']' ||
141
- char === '?' ||
142
- char === '=' ||
143
- char === '{' ||
144
- char === '}' ||
145
- code === 32 || // SP
146
- code === 9 // HT
143
+ code === 0x22 || // "
144
+ code === 0x28 || // (
145
+ code === 0x29 || // )
146
+ code === 0x2C || // ,
147
+ code === 0x2F || // /
148
+ code === 0x3A || // :
149
+ code === 0x3B || // ;
150
+ code === 0x3C || // <
151
+ code === 0x3D || // =
152
+ code === 0x3E || // >
153
+ code === 0x3F || // ?
154
+ code === 0x40 || // @
155
+ code === 0x5B || // [
156
+ code === 0x5C || // \
157
+ code === 0x5D || // ]
158
+ code === 0x7B || // {
159
+ code === 0x7D // }
147
160
  ) {
148
161
  return false
149
162
  }
@@ -190,6 +203,7 @@ function failWebsocketConnection (ws, reason) {
190
203
  }
191
204
 
192
205
  module.exports = {
206
+ isConnecting,
193
207
  isEstablished,
194
208
  isClosing,
195
209
  isClosed,
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const { webidl } = require('../fetch/webidl')
4
- const { URLSerializer } = require('../fetch/dataURL')
4
+ const { URLSerializer } = require('../fetch/data-url')
5
5
  const { getGlobalOrigin } = require('../fetch/global')
6
- const { staticPropertyDescriptors, states, opcodes, emptyBuffer } = require('./constants')
6
+ const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes, emptyBuffer } = require('./constants')
7
7
  const {
8
8
  kWebSocketURL,
9
9
  kReadyState,
@@ -13,12 +13,20 @@ const {
13
13
  kSentClose,
14
14
  kByteParser
15
15
  } = require('./symbols')
16
- const { isEstablished, isClosing, isValidSubprotocol, failWebsocketConnection, fireEvent } = require('./util')
16
+ const {
17
+ isConnecting,
18
+ isEstablished,
19
+ isClosed,
20
+ isClosing,
21
+ isValidSubprotocol,
22
+ failWebsocketConnection,
23
+ fireEvent
24
+ } = require('./util')
17
25
  const { establishWebSocketConnection } = require('./connection')
18
26
  const { WebsocketFrameSend } = require('./frame')
19
27
  const { ByteParser } = require('./receiver')
20
- const { kEnumerableProperty, isBlobLike } = require('../core/util')
21
- const { getGlobalDispatcher } = require('../global')
28
+ const { kEnumerableProperty, isBlobLike } = require('../../core/util')
29
+ const { getGlobalDispatcher } = require('../../global')
22
30
  const { types } = require('node:util')
23
31
 
24
32
  let experimentalWarned = false
@@ -132,6 +140,8 @@ class WebSocket extends EventTarget {
132
140
  // be CONNECTING (0).
133
141
  this[kReadyState] = WebSocket.CONNECTING
134
142
 
143
+ this[kSentClose] = sentCloseFrameState.NOT_SENT
144
+
135
145
  // The extensions attribute must initially return the empty string.
136
146
 
137
147
  // The protocol attribute must initially return the empty string.
@@ -184,7 +194,7 @@ class WebSocket extends EventTarget {
184
194
  }
185
195
 
186
196
  // 3. Run the first matching steps from the following list:
187
- if (this[kReadyState] === WebSocket.CLOSING || this[kReadyState] === WebSocket.CLOSED) {
197
+ if (isClosing(this) || isClosed(this)) {
188
198
  // If this's ready state is CLOSING (2) or CLOSED (3)
189
199
  // Do nothing.
190
200
  } else if (!isEstablished(this)) {
@@ -193,7 +203,7 @@ class WebSocket extends EventTarget {
193
203
  // to CLOSING (2).
194
204
  failWebsocketConnection(this, 'Connection was closed before it was established.')
195
205
  this[kReadyState] = WebSocket.CLOSING
196
- } else if (!isClosing(this)) {
206
+ } else if (this[kSentClose] === sentCloseFrameState.NOT_SENT) {
197
207
  // If the WebSocket closing handshake has not yet been started
198
208
  // Start the WebSocket closing handshake and set this's ready
199
209
  // state to CLOSING (2).
@@ -204,6 +214,8 @@ class WebSocket extends EventTarget {
204
214
  // - If reason is also present, then reasonBytes must be
205
215
  // provided in the Close message after the status code.
206
216
 
217
+ this[kSentClose] = sentCloseFrameState.PROCESSING
218
+
207
219
  const frame = new WebsocketFrameSend()
208
220
 
209
221
  // If neither code nor reason is present, the WebSocket Close
@@ -230,7 +242,7 @@ class WebSocket extends EventTarget {
230
242
 
231
243
  socket.write(frame.createFrame(opcodes.CLOSE), (err) => {
232
244
  if (!err) {
233
- this[kSentClose] = true
245
+ this[kSentClose] = sentCloseFrameState.SENT
234
246
  }
235
247
  })
236
248
 
@@ -258,7 +270,7 @@ class WebSocket extends EventTarget {
258
270
 
259
271
  // 1. If this's ready state is CONNECTING, then throw an
260
272
  // "InvalidStateError" DOMException.
261
- if (this[kReadyState] === WebSocket.CONNECTING) {
273
+ if (isConnecting(this)) {
262
274
  throw new DOMException('Sent before connected.', 'InvalidStateError')
263
275
  }
264
276