undici 6.17.0 → 6.18.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.
@@ -737,6 +737,7 @@ module.exports = {
737
737
  collectAnHTTPQuotedString,
738
738
  serializeAMimeType,
739
739
  removeChars,
740
+ removeHTTPWhitespace,
740
741
  minimizeSupportedMimeType,
741
742
  HTTP_TOKEN_CODEPOINTS,
742
743
  isomorphicDecode
@@ -8,7 +8,7 @@ const {
8
8
  kReceivedClose,
9
9
  kResponse
10
10
  } = require('./symbols')
11
- const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished } = require('./util')
11
+ const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = require('./util')
12
12
  const { channels } = require('../../core/diagnostics')
13
13
  const { CloseEvent } = require('./events')
14
14
  const { makeRequest } = require('../fetch/request')
@@ -31,7 +31,7 @@ try {
31
31
  * @param {URL} url
32
32
  * @param {string|string[]} protocols
33
33
  * @param {import('./websocket').WebSocket} ws
34
- * @param {(response: any) => void} onEstablish
34
+ * @param {(response: any, extensions: string[] | undefined) => void} onEstablish
35
35
  * @param {Partial<import('../../types/websocket').WebSocketInit>} options
36
36
  */
37
37
  function establishWebSocketConnection (url, protocols, client, ws, onEstablish, options) {
@@ -91,12 +91,11 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
91
91
  // 9. Let permessageDeflate be a user-agent defined
92
92
  // "permessage-deflate" extension header value.
93
93
  // https://github.com/mozilla/gecko-dev/blob/ce78234f5e653a5d3916813ff990f053510227bc/netwerk/protocol/websocket/WebSocketChannel.cpp#L2673
94
- // TODO: enable once permessage-deflate is supported
95
- const permessageDeflate = '' // 'permessage-deflate; 15'
94
+ const permessageDeflate = 'permessage-deflate; client_max_window_bits'
96
95
 
97
96
  // 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
98
97
  // request’s header list.
99
- // request.headersList.append('sec-websocket-extensions', permessageDeflate)
98
+ request.headersList.append('sec-websocket-extensions', permessageDeflate)
100
99
 
101
100
  // 11. Fetch request with useParallelQueue set to true, and
102
101
  // processResponse given response being these steps:
@@ -167,10 +166,15 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
167
166
  // header field to determine which extensions are requested is
168
167
  // discussed in Section 9.1.)
169
168
  const secExtension = response.headersList.get('Sec-WebSocket-Extensions')
169
+ let extensions
170
170
 
171
- if (secExtension !== null && secExtension !== permessageDeflate) {
172
- failWebsocketConnection(ws, 'Received different permessage-deflate than the one set.')
173
- return
171
+ if (secExtension !== null) {
172
+ extensions = parseExtensions(secExtension)
173
+
174
+ if (!extensions.has('permessage-deflate')) {
175
+ failWebsocketConnection(ws, 'Sec-WebSocket-Extensions header does not match.')
176
+ return
177
+ }
174
178
  }
175
179
 
176
180
  // 6. If the response includes a |Sec-WebSocket-Protocol| header field
@@ -206,7 +210,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
206
210
  })
207
211
  }
208
212
 
209
- onEstablish(response)
213
+ onEstablish(response, extensions)
210
214
  }
211
215
  })
212
216
 
@@ -290,6 +294,11 @@ function onSocketData (chunk) {
290
294
  */
291
295
  function onSocketClose () {
292
296
  const { ws } = this
297
+ const { [kResponse]: response } = ws
298
+
299
+ response.socket.off('data', onSocketData)
300
+ response.socket.off('close', onSocketClose)
301
+ response.socket.off('error', onSocketError)
293
302
 
294
303
  // If the TCP connection was closed after the
295
304
  // WebSocket closing handshake was completed, the WebSocket connection
@@ -46,6 +46,13 @@ const parserStates = {
46
46
 
47
47
  const emptyBuffer = Buffer.allocUnsafe(0)
48
48
 
49
+ const sendHints = {
50
+ string: 1,
51
+ typedArray: 2,
52
+ arrayBuffer: 3,
53
+ blob: 4
54
+ }
55
+
49
56
  module.exports = {
50
57
  uid,
51
58
  sentCloseFrameState,
@@ -54,5 +61,6 @@ module.exports = {
54
61
  opcodes,
55
62
  maxUnsigned16Bit,
56
63
  parserStates,
57
- emptyBuffer
64
+ emptyBuffer,
65
+ sendHints
58
66
  }
@@ -0,0 +1,70 @@
1
+ 'use strict'
2
+
3
+ const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = require('node:zlib')
4
+ const { isValidClientWindowBits } = require('./util')
5
+
6
+ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
7
+ const kBuffer = Symbol('kBuffer')
8
+ const kLength = Symbol('kLength')
9
+
10
+ class PerMessageDeflate {
11
+ /** @type {import('node:zlib').InflateRaw} */
12
+ #inflate
13
+
14
+ #options = {}
15
+
16
+ constructor (extensions) {
17
+ this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
18
+ this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
19
+ }
20
+
21
+ decompress (chunk, fin, callback) {
22
+ // An endpoint uses the following algorithm to decompress a message.
23
+ // 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
24
+ // payload of the message.
25
+ // 2. Decompress the resulting data using DEFLATE.
26
+
27
+ if (!this.#inflate) {
28
+ let windowBits = Z_DEFAULT_WINDOWBITS
29
+
30
+ if (this.#options.serverMaxWindowBits) { // empty values default to Z_DEFAULT_WINDOWBITS
31
+ if (!isValidClientWindowBits(this.#options.serverMaxWindowBits)) {
32
+ callback(new Error('Invalid server_max_window_bits'))
33
+ return
34
+ }
35
+
36
+ windowBits = Number.parseInt(this.#options.serverMaxWindowBits)
37
+ }
38
+
39
+ this.#inflate = createInflateRaw({ windowBits })
40
+ this.#inflate[kBuffer] = []
41
+ this.#inflate[kLength] = 0
42
+
43
+ this.#inflate.on('data', (data) => {
44
+ this.#inflate[kBuffer].push(data)
45
+ this.#inflate[kLength] += data.length
46
+ })
47
+
48
+ this.#inflate.on('error', (err) => {
49
+ this.#inflate = null
50
+ callback(err)
51
+ })
52
+ }
53
+
54
+ this.#inflate.write(chunk)
55
+ if (fin) {
56
+ this.#inflate.write(tail)
57
+ }
58
+
59
+ this.#inflate.flush(() => {
60
+ const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength])
61
+
62
+ this.#inflate[kBuffer].length = 0
63
+ this.#inflate[kLength] = 0
64
+
65
+ callback(null, full)
66
+ })
67
+ }
68
+ }
69
+
70
+ module.exports = { PerMessageDeflate }
@@ -17,6 +17,7 @@ const {
17
17
  } = require('./util')
18
18
  const { WebsocketFrameSend } = require('./frame')
19
19
  const { closeWebSocketConnection } = require('./connection')
20
+ const { PerMessageDeflate } = require('./permessage-deflate')
20
21
 
21
22
  // This code was influenced by ws released under the MIT license.
22
23
  // Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
@@ -33,10 +34,18 @@ class ByteParser extends Writable {
33
34
  #info = {}
34
35
  #fragments = []
35
36
 
36
- constructor (ws) {
37
+ /** @type {Map<string, PerMessageDeflate>} */
38
+ #extensions
39
+
40
+ constructor (ws, extensions) {
37
41
  super()
38
42
 
39
43
  this.ws = ws
44
+ this.#extensions = extensions == null ? new Map() : extensions
45
+
46
+ if (this.#extensions.has('permessage-deflate')) {
47
+ this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
48
+ }
40
49
  }
41
50
 
42
51
  /**
@@ -91,7 +100,16 @@ class ByteParser extends Writable {
91
100
  // the negotiated extensions defines the meaning of such a nonzero
92
101
  // value, the receiving endpoint MUST _Fail the WebSocket
93
102
  // Connection_.
94
- if (rsv1 !== 0 || rsv2 !== 0 || rsv3 !== 0) {
103
+ // This document allocates the RSV1 bit of the WebSocket header for
104
+ // PMCEs and calls the bit the "Per-Message Compressed" bit. On a
105
+ // WebSocket connection where a PMCE is in use, this bit indicates
106
+ // whether a message is compressed or not.
107
+ if (rsv1 !== 0 && !this.#extensions.has('permessage-deflate')) {
108
+ failWebsocketConnection(this.ws, 'Expected RSV1 to be clear.')
109
+ return
110
+ }
111
+
112
+ if (rsv2 !== 0 || rsv3 !== 0) {
95
113
  failWebsocketConnection(this.ws, 'RSV1, RSV2, RSV3 must be clear')
96
114
  return
97
115
  }
@@ -122,7 +140,7 @@ class ByteParser extends Writable {
122
140
  return
123
141
  }
124
142
 
125
- if (isContinuationFrame(opcode) && this.#fragments.length === 0) {
143
+ if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) {
126
144
  failWebsocketConnection(this.ws, 'Unexpected continuation frame')
127
145
  return
128
146
  }
@@ -138,6 +156,7 @@ class ByteParser extends Writable {
138
156
 
139
157
  if (isTextBinaryFrame(opcode)) {
140
158
  this.#info.binaryType = opcode
159
+ this.#info.compressed = rsv1 !== 0
141
160
  }
142
161
 
143
162
  this.#info.opcode = opcode
@@ -185,21 +204,50 @@ class ByteParser extends Writable {
185
204
 
186
205
  if (isControlFrame(this.#info.opcode)) {
187
206
  this.#loop = this.parseControlFrame(body)
207
+ this.#state = parserStates.INFO
188
208
  } else {
189
- this.#fragments.push(body)
190
-
191
- // If the frame is not fragmented, a message has been received.
192
- // If the frame is fragmented, it will terminate with a fin bit set
193
- // and an opcode of 0 (continuation), therefore we handle that when
194
- // parsing continuation frames, not here.
195
- if (!this.#info.fragmented && this.#info.fin) {
196
- const fullMessage = Buffer.concat(this.#fragments)
197
- websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
198
- this.#fragments.length = 0
209
+ if (!this.#info.compressed) {
210
+ this.#fragments.push(body)
211
+
212
+ // If the frame is not fragmented, a message has been received.
213
+ // If the frame is fragmented, it will terminate with a fin bit set
214
+ // and an opcode of 0 (continuation), therefore we handle that when
215
+ // parsing continuation frames, not here.
216
+ if (!this.#info.fragmented && this.#info.fin) {
217
+ const fullMessage = Buffer.concat(this.#fragments)
218
+ websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
219
+ this.#fragments.length = 0
220
+ }
221
+
222
+ this.#state = parserStates.INFO
223
+ } else {
224
+ this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
225
+ if (error) {
226
+ closeWebSocketConnection(this.ws, 1007, error.message, error.message.length)
227
+ return
228
+ }
229
+
230
+ this.#fragments.push(data)
231
+
232
+ if (!this.#info.fin) {
233
+ this.#state = parserStates.INFO
234
+ this.#loop = true
235
+ this.run(callback)
236
+ return
237
+ }
238
+
239
+ websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
240
+
241
+ this.#loop = true
242
+ this.#state = parserStates.INFO
243
+ this.run(callback)
244
+ this.#fragments.length = 0
245
+ })
246
+
247
+ this.#loop = false
248
+ break
199
249
  }
200
250
  }
201
-
202
- this.#state = parserStates.INFO
203
251
  }
204
252
  }
205
253
  }
@@ -333,7 +381,6 @@ class ByteParser extends Writable {
333
381
  this.ws[kReadyState] = states.CLOSING
334
382
  this.ws[kReceivedClose] = true
335
383
 
336
- this.end()
337
384
  return false
338
385
  } else if (opcode === opcodes.PING) {
339
386
  // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
@@ -0,0 +1,85 @@
1
+ 'use strict'
2
+
3
+ const { WebsocketFrameSend } = require('./frame')
4
+ const { opcodes, sendHints } = require('./constants')
5
+
6
+ /** @type {Uint8Array} */
7
+ const FastBuffer = Buffer[Symbol.species]
8
+
9
+ class SendQueue {
10
+ #queued = new Set()
11
+ #size = 0
12
+
13
+ /** @type {import('net').Socket} */
14
+ #socket
15
+
16
+ constructor (socket) {
17
+ this.#socket = socket
18
+ }
19
+
20
+ add (item, cb, hint) {
21
+ if (hint !== sendHints.blob) {
22
+ const data = clone(item, hint)
23
+
24
+ if (this.#size === 0) {
25
+ this.#dispatch(data, cb, hint)
26
+ } else {
27
+ this.#queued.add([data, cb, true, hint])
28
+ this.#size++
29
+
30
+ this.#run()
31
+ }
32
+
33
+ return
34
+ }
35
+
36
+ const promise = item.arrayBuffer()
37
+ const queue = [null, cb, false, hint]
38
+ promise.then((ab) => {
39
+ queue[0] = clone(ab, hint)
40
+ queue[2] = true
41
+
42
+ this.#run()
43
+ })
44
+
45
+ this.#queued.add(queue)
46
+ this.#size++
47
+ }
48
+
49
+ #run () {
50
+ for (const queued of this.#queued) {
51
+ const [data, cb, done, hint] = queued
52
+
53
+ if (!done) return
54
+
55
+ this.#queued.delete(queued)
56
+ this.#size--
57
+
58
+ this.#dispatch(data, cb, hint)
59
+ }
60
+ }
61
+
62
+ #dispatch (data, cb, hint) {
63
+ const frame = new WebsocketFrameSend()
64
+ const opcode = hint === sendHints.string ? opcodes.TEXT : opcodes.BINARY
65
+
66
+ frame.frameData = data
67
+ const buffer = frame.createFrame(opcode)
68
+
69
+ this.#socket.write(buffer, cb)
70
+ }
71
+ }
72
+
73
+ function clone (data, hint) {
74
+ switch (hint) {
75
+ case sendHints.string:
76
+ return Buffer.from(data)
77
+ case sendHints.arrayBuffer:
78
+ case sendHints.blob:
79
+ return new FastBuffer(data)
80
+ case sendHints.typedArray:
81
+ return Buffer.copyBytesFrom(data)
82
+ }
83
+ }
84
+
85
+ module.exports = { SendQueue }
@@ -4,6 +4,7 @@ const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = requ
4
4
  const { states, opcodes } = require('./constants')
5
5
  const { ErrorEvent, createFastMessageEvent } = require('./events')
6
6
  const { isUtf8 } = require('node:buffer')
7
+ const { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require('../fetch/data-url')
7
8
 
8
9
  /* globals Blob */
9
10
 
@@ -234,6 +235,48 @@ function isValidOpcode (opcode) {
234
235
  return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode)
235
236
  }
236
237
 
238
+ /**
239
+ * Parses a Sec-WebSocket-Extensions header value.
240
+ * @param {string} extensions
241
+ * @returns {Map<string, string>}
242
+ */
243
+ // TODO(@Uzlopak, @KhafraDev): make compliant https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
244
+ function parseExtensions (extensions) {
245
+ const position = { position: 0 }
246
+ const extensionList = new Map()
247
+
248
+ while (position.position < extensions.length) {
249
+ const pair = collectASequenceOfCodePointsFast(';', extensions, position)
250
+ const [name, value = ''] = pair.split('=')
251
+
252
+ extensionList.set(
253
+ removeHTTPWhitespace(name, true, false),
254
+ removeHTTPWhitespace(value, false, true)
255
+ )
256
+
257
+ position.position++
258
+ }
259
+
260
+ return extensionList
261
+ }
262
+
263
+ /**
264
+ * @see https://www.rfc-editor.org/rfc/rfc7692#section-7.1.2.2
265
+ * @description "client-max-window-bits = 1*DIGIT"
266
+ * @param {string} value
267
+ */
268
+ function isValidClientWindowBits (value) {
269
+ for (let i = 0; i < value.length; i++) {
270
+ const byte = value.charCodeAt(i)
271
+
272
+ if (byte < 0x30 || byte > 0x39) {
273
+ return false
274
+ }
275
+ }
276
+
277
+ return true
278
+ }
279
+
237
280
  // https://nodejs.org/api/intl.html#detecting-internationalization-support
238
281
  const hasIntl = typeof process.versions.icu === 'string'
239
282
  const fatalDecoder = hasIntl ? new TextDecoder('utf-8', { fatal: true }) : undefined
@@ -265,5 +308,7 @@ module.exports = {
265
308
  isControlFrame,
266
309
  isContinuationFrame,
267
310
  isTextBinaryFrame,
268
- isValidOpcode
311
+ isValidOpcode,
312
+ parseExtensions,
313
+ isValidClientWindowBits
269
314
  }
@@ -3,7 +3,7 @@
3
3
  const { webidl } = require('../fetch/webidl')
4
4
  const { URLSerializer } = require('../fetch/data-url')
5
5
  const { environmentSettingsObject } = require('../fetch/util')
6
- const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes } = require('./constants')
6
+ const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints } = require('./constants')
7
7
  const {
8
8
  kWebSocketURL,
9
9
  kReadyState,
@@ -21,17 +21,15 @@ const {
21
21
  fireEvent
22
22
  } = require('./util')
23
23
  const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
24
- const { WebsocketFrameSend } = require('./frame')
25
24
  const { ByteParser } = require('./receiver')
26
25
  const { kEnumerableProperty, isBlobLike } = require('../../core/util')
27
26
  const { getGlobalDispatcher } = require('../../global')
28
27
  const { types } = require('node:util')
29
28
  const { ErrorEvent, CloseEvent } = require('./events')
29
+ const { SendQueue } = require('./sender')
30
30
 
31
31
  let experimentalWarned = false
32
32
 
33
- const FastBuffer = Buffer[Symbol.species]
34
-
35
33
  // https://websockets.spec.whatwg.org/#interface-definition
36
34
  class WebSocket extends EventTarget {
37
35
  #events = {
@@ -45,6 +43,9 @@ class WebSocket extends EventTarget {
45
43
  #protocol = ''
46
44
  #extensions = ''
47
45
 
46
+ /** @type {SendQueue} */
47
+ #sendQueue
48
+
48
49
  /**
49
50
  * @param {string} url
50
51
  * @param {string|string[]} protocols
@@ -135,7 +136,7 @@ class WebSocket extends EventTarget {
135
136
  protocols,
136
137
  client,
137
138
  this,
138
- (response) => this.#onConnectionEstablished(response),
139
+ (response, extensions) => this.#onConnectionEstablished(response, extensions),
139
140
  options
140
141
  )
141
142
 
@@ -229,9 +230,6 @@ class WebSocket extends EventTarget {
229
230
  return
230
231
  }
231
232
 
232
- /** @type {import('stream').Duplex} */
233
- const socket = this[kResponse].socket
234
-
235
233
  // If data is a string
236
234
  if (typeof data === 'string') {
237
235
  // If the WebSocket connection is established and the WebSocket
@@ -245,14 +243,12 @@ class WebSocket extends EventTarget {
245
243
  // the bufferedAmount attribute by the number of bytes needed to
246
244
  // express the argument as UTF-8.
247
245
 
248
- const value = Buffer.from(data)
249
- const frame = new WebsocketFrameSend(value)
250
- const buffer = frame.createFrame(opcodes.TEXT)
246
+ const length = Buffer.byteLength(data)
251
247
 
252
- this.#bufferedAmount += value.byteLength
253
- socket.write(buffer, () => {
254
- this.#bufferedAmount -= value.byteLength
255
- })
248
+ this.#bufferedAmount += length
249
+ this.#sendQueue.add(data, () => {
250
+ this.#bufferedAmount -= length
251
+ }, sendHints.string)
256
252
  } else if (types.isArrayBuffer(data)) {
257
253
  // If the WebSocket connection is established, and the WebSocket
258
254
  // closing handshake has not yet started, then the user agent must
@@ -266,14 +262,10 @@ class WebSocket extends EventTarget {
266
262
  // increase the bufferedAmount attribute by the length of the
267
263
  // ArrayBuffer in bytes.
268
264
 
269
- const value = new FastBuffer(data)
270
- const frame = new WebsocketFrameSend(value)
271
- const buffer = frame.createFrame(opcodes.BINARY)
272
-
273
- this.#bufferedAmount += value.byteLength
274
- socket.write(buffer, () => {
275
- this.#bufferedAmount -= value.byteLength
276
- })
265
+ this.#bufferedAmount += data.byteLength
266
+ this.#sendQueue.add(data, () => {
267
+ this.#bufferedAmount -= data.byteLength
268
+ }, sendHints.arrayBuffer)
277
269
  } else if (ArrayBuffer.isView(data)) {
278
270
  // If the WebSocket connection is established, and the WebSocket
279
271
  // closing handshake has not yet started, then the user agent must
@@ -287,15 +279,10 @@ class WebSocket extends EventTarget {
287
279
  // not throw an exception must increase the bufferedAmount attribute
288
280
  // by the length of data’s buffer in bytes.
289
281
 
290
- const ab = new FastBuffer(data.buffer, data.byteOffset, data.byteLength)
291
-
292
- const frame = new WebsocketFrameSend(ab)
293
- const buffer = frame.createFrame(opcodes.BINARY)
294
-
295
- this.#bufferedAmount += ab.byteLength
296
- socket.write(buffer, () => {
297
- this.#bufferedAmount -= ab.byteLength
298
- })
282
+ this.#bufferedAmount += data.byteLength
283
+ this.#sendQueue.add(data, () => {
284
+ this.#bufferedAmount -= data.byteLength
285
+ }, sendHints.typedArray)
299
286
  } else if (isBlobLike(data)) {
300
287
  // If the WebSocket connection is established, and the WebSocket
301
288
  // closing handshake has not yet started, then the user agent must
@@ -308,18 +295,10 @@ class WebSocket extends EventTarget {
308
295
  // an exception must increase the bufferedAmount attribute by the size
309
296
  // of the Blob object’s raw data, in bytes.
310
297
 
311
- const frame = new WebsocketFrameSend()
312
-
313
- data.arrayBuffer().then((ab) => {
314
- const value = new FastBuffer(ab)
315
- frame.frameData = value
316
- const buffer = frame.createFrame(opcodes.BINARY)
317
-
318
- this.#bufferedAmount += value.byteLength
319
- socket.write(buffer, () => {
320
- this.#bufferedAmount -= value.byteLength
321
- })
322
- })
298
+ this.#bufferedAmount += data.size
299
+ this.#sendQueue.add(data, () => {
300
+ this.#bufferedAmount -= data.size
301
+ }, sendHints.blob)
323
302
  }
324
303
  }
325
304
 
@@ -458,18 +437,20 @@ class WebSocket extends EventTarget {
458
437
  /**
459
438
  * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
460
439
  */
461
- #onConnectionEstablished (response) {
440
+ #onConnectionEstablished (response, parsedExtensions) {
462
441
  // processResponse is called when the "response’s header list has been received and initialized."
463
442
  // once this happens, the connection is open
464
443
  this[kResponse] = response
465
444
 
466
- const parser = new ByteParser(this)
445
+ const parser = new ByteParser(this, parsedExtensions)
467
446
  parser.on('drain', onParserDrain)
468
447
  parser.on('error', onParserError.bind(this))
469
448
 
470
449
  response.socket.ws = this
471
450
  this[kByteParser] = parser
472
451
 
452
+ this.#sendQueue = new SendQueue(response.socket)
453
+
473
454
  // 1. Change the ready state to OPEN (1).
474
455
  this[kReadyState] = states.OPEN
475
456
 
@@ -558,7 +539,7 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
558
539
  },
559
540
  {
560
541
  key: 'dispatcher',
561
- converter: (V) => V,
542
+ converter: webidl.converters.any,
562
543
  defaultValue: () => getGlobalDispatcher()
563
544
  },
564
545
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.17.0",
3
+ "version": "6.18.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": {