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.
- package/lib/web/fetch/data-url.js +1 -0
- package/lib/web/websocket/connection.js +18 -9
- package/lib/web/websocket/constants.js +9 -1
- package/lib/web/websocket/permessage-deflate.js +70 -0
- package/lib/web/websocket/receiver.js +63 -16
- package/lib/web/websocket/sender.js +85 -0
- package/lib/web/websocket/util.js +46 -1
- package/lib/web/websocket/websocket.js +28 -47
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.#
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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,
|
|
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
|
|
249
|
-
const frame = new WebsocketFrameSend(value)
|
|
250
|
-
const buffer = frame.createFrame(opcodes.TEXT)
|
|
246
|
+
const length = Buffer.byteLength(data)
|
|
251
247
|
|
|
252
|
-
this.#bufferedAmount +=
|
|
253
|
-
|
|
254
|
-
this.#bufferedAmount -=
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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:
|
|
542
|
+
converter: webidl.converters.any,
|
|
562
543
|
defaultValue: () => getGlobalDispatcher()
|
|
563
544
|
},
|
|
564
545
|
{
|