undici 6.24.0 → 6.25.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/docs/docs/api/Client.md +2 -0
- package/docs/docs/api/WebSocket.md +0 -15
- package/lib/dispatcher/agent.js +2 -1
- package/lib/dispatcher/client.js +3 -2
- package/lib/dispatcher/dispatcher-base.js +9 -1
- package/lib/dispatcher/pool-base.js +2 -2
- package/lib/dispatcher/pool.js +2 -2
- package/lib/web/websocket/permessage-deflate.js +13 -36
- package/lib/web/websocket/receiver.js +84 -26
- package/lib/web/websocket/websocket.js +5 -22
- package/package.json +1 -1
- package/types/client.d.ts +11 -0
- package/types/websocket.d.ts +1 -8
package/docs/docs/api/Client.md
CHANGED
|
@@ -26,6 +26,8 @@ Returns: `Client`
|
|
|
26
26
|
* **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `2e3` - A number of milliseconds subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 2 seconds.
|
|
27
27
|
* **maxHeaderSize** `number | null` (optional) - Default: `--max-http-header-size` or `16384` - The maximum length of request headers in bytes. Defaults to Node.js' --max-http-header-size or 16KiB.
|
|
28
28
|
* **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable.
|
|
29
|
+
* **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options.
|
|
30
|
+
* **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit.
|
|
29
31
|
* **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections.
|
|
30
32
|
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
|
|
31
33
|
* **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body.
|
|
@@ -20,7 +20,6 @@ When passing an object as the second argument, the following options are availab
|
|
|
20
20
|
* **protocols** `string | string[]` (optional) - Subprotocol(s) to request the server use.
|
|
21
21
|
* **dispatcher** `Dispatcher` (optional) - A custom [`Dispatcher`](/docs/docs/api/Dispatcher.md) to use for the connection.
|
|
22
22
|
* **headers** `HeadersInit` (optional) - Custom headers to include in the WebSocket handshake request.
|
|
23
|
-
* **maxDecompressedMessageSize** `number` (optional) - Maximum allowed size in bytes for decompressed messages when using the `permessage-deflate` extension. **Default:** `4194304` (4 MB).
|
|
24
23
|
|
|
25
24
|
### Example:
|
|
26
25
|
|
|
@@ -45,20 +44,6 @@ import { WebSocket } from 'undici'
|
|
|
45
44
|
const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat'])
|
|
46
45
|
```
|
|
47
46
|
|
|
48
|
-
### Example with custom decompression limit:
|
|
49
|
-
|
|
50
|
-
To protect against decompression bombs (small compressed payloads that expand to very large sizes), you can set a custom limit:
|
|
51
|
-
|
|
52
|
-
```mjs
|
|
53
|
-
import { WebSocket } from 'undici'
|
|
54
|
-
|
|
55
|
-
const ws = new WebSocket('wss://echo.websocket.events', {
|
|
56
|
-
maxDecompressedMessageSize: 1 * 1024 * 1024
|
|
57
|
-
})
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
> ⚠️ **Security Note**: The `maxDecompressedMessageSize` option protects against memory exhaustion attacks where a malicious server sends a small compressed payload that decompresses to an extremely large size. If you increase this limit significantly above the default, ensure your application can handle the increased memory usage.
|
|
61
|
-
|
|
62
47
|
## Read More
|
|
63
48
|
|
|
64
49
|
- [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
package/lib/dispatcher/agent.js
CHANGED
|
@@ -24,7 +24,6 @@ function defaultFactory (origin, opts) {
|
|
|
24
24
|
|
|
25
25
|
class Agent extends DispatcherBase {
|
|
26
26
|
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
|
27
|
-
super()
|
|
28
27
|
|
|
29
28
|
if (typeof factory !== 'function') {
|
|
30
29
|
throw new InvalidArgumentError('factory must be a function.')
|
|
@@ -38,6 +37,8 @@ class Agent extends DispatcherBase {
|
|
|
38
37
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
super(options)
|
|
41
|
+
|
|
41
42
|
if (connect && typeof connect !== 'function') {
|
|
42
43
|
connect = { ...connect }
|
|
43
44
|
}
|
package/lib/dispatcher/client.js
CHANGED
|
@@ -106,9 +106,10 @@ class Client extends DispatcherBase {
|
|
|
106
106
|
autoSelectFamilyAttemptTimeout,
|
|
107
107
|
// h2
|
|
108
108
|
maxConcurrentStreams,
|
|
109
|
-
allowH2
|
|
109
|
+
allowH2,
|
|
110
|
+
webSocket
|
|
110
111
|
} = {}) {
|
|
111
|
-
super()
|
|
112
|
+
super({ webSocket })
|
|
112
113
|
|
|
113
114
|
if (keepAlive !== undefined) {
|
|
114
115
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
|
@@ -11,15 +11,23 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = requ
|
|
|
11
11
|
const kOnDestroyed = Symbol('onDestroyed')
|
|
12
12
|
const kOnClosed = Symbol('onClosed')
|
|
13
13
|
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
|
14
|
+
const kWebSocketOptions = Symbol('webSocketOptions')
|
|
14
15
|
|
|
15
16
|
class DispatcherBase extends Dispatcher {
|
|
16
|
-
constructor () {
|
|
17
|
+
constructor (opts) {
|
|
17
18
|
super()
|
|
18
19
|
|
|
19
20
|
this[kDestroyed] = false
|
|
20
21
|
this[kOnDestroyed] = null
|
|
21
22
|
this[kClosed] = false
|
|
22
23
|
this[kOnClosed] = []
|
|
24
|
+
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get webSocketOptions () {
|
|
28
|
+
return {
|
|
29
|
+
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
|
30
|
+
}
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
get destroyed () {
|
|
@@ -19,8 +19,8 @@ const kRemoveClient = Symbol('remove client')
|
|
|
19
19
|
const kStats = Symbol('stats')
|
|
20
20
|
|
|
21
21
|
class PoolBase extends DispatcherBase {
|
|
22
|
-
constructor () {
|
|
23
|
-
super()
|
|
22
|
+
constructor (opts) {
|
|
23
|
+
super(opts)
|
|
24
24
|
|
|
25
25
|
this[kQueue] = new FixedQueue()
|
|
26
26
|
this[kClients] = []
|
package/lib/dispatcher/pool.js
CHANGED
|
@@ -37,8 +37,6 @@ class Pool extends PoolBase {
|
|
|
37
37
|
allowH2,
|
|
38
38
|
...options
|
|
39
39
|
} = {}) {
|
|
40
|
-
super()
|
|
41
|
-
|
|
42
40
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
|
43
41
|
throw new InvalidArgumentError('invalid connections')
|
|
44
42
|
}
|
|
@@ -63,6 +61,8 @@ class Pool extends PoolBase {
|
|
|
63
61
|
})
|
|
64
62
|
}
|
|
65
63
|
|
|
64
|
+
super(options)
|
|
65
|
+
|
|
66
66
|
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
|
67
67
|
? options.interceptors.Pool
|
|
68
68
|
: []
|
|
@@ -8,45 +8,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
|
|
8
8
|
const kBuffer = Symbol('kBuffer')
|
|
9
9
|
const kLength = Symbol('kLength')
|
|
10
10
|
|
|
11
|
-
// Default maximum decompressed message size: 4 MB
|
|
12
|
-
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
|
13
|
-
|
|
14
11
|
class PerMessageDeflate {
|
|
15
12
|
/** @type {import('node:zlib').InflateRaw} */
|
|
16
13
|
#inflate
|
|
17
14
|
|
|
18
15
|
#options = {}
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
#maxDecompressedSize
|
|
22
|
-
|
|
23
|
-
/** @type {boolean} */
|
|
24
|
-
#aborted = false
|
|
25
|
-
|
|
26
|
-
/** @type {Function|null} */
|
|
27
|
-
#currentCallback = null
|
|
17
|
+
#maxPayloadSize = 0
|
|
28
18
|
|
|
29
19
|
/**
|
|
30
20
|
* @param {Map<string, string>} extensions
|
|
31
|
-
* @param {{ maxDecompressedMessageSize?: number }} [options]
|
|
32
21
|
*/
|
|
33
|
-
constructor (extensions, options
|
|
22
|
+
constructor (extensions, options) {
|
|
34
23
|
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
|
35
24
|
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
|
36
|
-
|
|
25
|
+
|
|
26
|
+
this.#maxPayloadSize = options.maxPayloadSize
|
|
37
27
|
}
|
|
38
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Decompress a compressed payload.
|
|
31
|
+
* @param {Buffer} chunk Compressed data
|
|
32
|
+
* @param {boolean} fin Final fragment flag
|
|
33
|
+
* @param {Function} callback Callback function
|
|
34
|
+
*/
|
|
39
35
|
decompress (chunk, fin, callback) {
|
|
40
36
|
// An endpoint uses the following algorithm to decompress a message.
|
|
41
37
|
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
|
42
38
|
// payload of the message.
|
|
43
39
|
// 2. Decompress the resulting data using DEFLATE.
|
|
44
|
-
|
|
45
|
-
if (this.#aborted) {
|
|
46
|
-
callback(new MessageSizeExceededError())
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
40
|
if (!this.#inflate) {
|
|
51
41
|
let windowBits = Z_DEFAULT_WINDOWBITS
|
|
52
42
|
|
|
@@ -69,23 +59,12 @@ class PerMessageDeflate {
|
|
|
69
59
|
this.#inflate[kLength] = 0
|
|
70
60
|
|
|
71
61
|
this.#inflate.on('data', (data) => {
|
|
72
|
-
if (this.#aborted) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
62
|
this.#inflate[kLength] += data.length
|
|
77
63
|
|
|
78
|
-
if (this.#inflate[kLength] > this.#
|
|
79
|
-
|
|
64
|
+
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
|
65
|
+
callback(new MessageSizeExceededError())
|
|
80
66
|
this.#inflate.removeAllListeners()
|
|
81
|
-
this.#inflate.destroy()
|
|
82
67
|
this.#inflate = null
|
|
83
|
-
|
|
84
|
-
if (this.#currentCallback) {
|
|
85
|
-
const cb = this.#currentCallback
|
|
86
|
-
this.#currentCallback = null
|
|
87
|
-
cb(new MessageSizeExceededError())
|
|
88
|
-
}
|
|
89
68
|
return
|
|
90
69
|
}
|
|
91
70
|
|
|
@@ -98,14 +77,13 @@ class PerMessageDeflate {
|
|
|
98
77
|
})
|
|
99
78
|
}
|
|
100
79
|
|
|
101
|
-
this.#currentCallback = callback
|
|
102
80
|
this.#inflate.write(chunk)
|
|
103
81
|
if (fin) {
|
|
104
82
|
this.#inflate.write(tail)
|
|
105
83
|
}
|
|
106
84
|
|
|
107
85
|
this.#inflate.flush(() => {
|
|
108
|
-
if (
|
|
86
|
+
if (!this.#inflate) {
|
|
109
87
|
return
|
|
110
88
|
}
|
|
111
89
|
|
|
@@ -113,7 +91,6 @@ class PerMessageDeflate {
|
|
|
113
91
|
|
|
114
92
|
this.#inflate[kBuffer].length = 0
|
|
115
93
|
this.#inflate[kLength] = 0
|
|
116
|
-
this.#currentCallback = null
|
|
117
94
|
|
|
118
95
|
callback(null, full)
|
|
119
96
|
})
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
const { WebsocketFrameSend } = require('./frame')
|
|
19
19
|
const { closeWebSocketConnection } = require('./connection')
|
|
20
20
|
const { PerMessageDeflate } = require('./permessage-deflate')
|
|
21
|
+
const { MessageSizeExceededError } = require('../../core/errors')
|
|
21
22
|
|
|
22
23
|
// This code was influenced by ws released under the MIT license.
|
|
23
24
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
@@ -26,6 +27,7 @@ const { PerMessageDeflate } = require('./permessage-deflate')
|
|
|
26
27
|
|
|
27
28
|
class ByteParser extends Writable {
|
|
28
29
|
#buffers = []
|
|
30
|
+
#fragmentsBytes = 0
|
|
29
31
|
#byteOffset = 0
|
|
30
32
|
#loop = false
|
|
31
33
|
|
|
@@ -37,20 +39,20 @@ class ByteParser extends Writable {
|
|
|
37
39
|
/** @type {Map<string, PerMessageDeflate>} */
|
|
38
40
|
#extensions
|
|
39
41
|
|
|
40
|
-
/** @type {
|
|
41
|
-
#
|
|
42
|
+
/** @type {number} */
|
|
43
|
+
#maxPayloadSize
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* @param {import('./websocket').WebSocket} ws
|
|
45
47
|
* @param {Map<string, string>|null} extensions
|
|
46
|
-
* @param {{
|
|
48
|
+
* @param {{ maxPayloadSize?: number }} [options]
|
|
47
49
|
*/
|
|
48
50
|
constructor (ws, extensions, options = {}) {
|
|
49
51
|
super()
|
|
50
52
|
|
|
51
53
|
this.ws = ws
|
|
52
54
|
this.#extensions = extensions == null ? new Map() : extensions
|
|
53
|
-
this.#
|
|
55
|
+
this.#maxPayloadSize = options.maxPayloadSize ?? 0
|
|
54
56
|
|
|
55
57
|
if (this.#extensions.has('permessage-deflate')) {
|
|
56
58
|
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
|
@@ -69,6 +71,19 @@ class ByteParser extends Writable {
|
|
|
69
71
|
this.run(callback)
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
#validatePayloadLength () {
|
|
75
|
+
if (
|
|
76
|
+
this.#maxPayloadSize > 0 &&
|
|
77
|
+
!isControlFrame(this.#info.opcode) &&
|
|
78
|
+
this.#info.payloadLength > this.#maxPayloadSize
|
|
79
|
+
) {
|
|
80
|
+
failWebsocketConnection(this.ws, 'Payload size exceeds maximum allowed size')
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
/**
|
|
73
88
|
* Runs whenever a new chunk is received.
|
|
74
89
|
* Callback is called whenever there are no more chunks buffering,
|
|
@@ -157,6 +172,10 @@ class ByteParser extends Writable {
|
|
|
157
172
|
if (payloadLength <= 125) {
|
|
158
173
|
this.#info.payloadLength = payloadLength
|
|
159
174
|
this.#state = parserStates.READ_DATA
|
|
175
|
+
|
|
176
|
+
if (!this.#validatePayloadLength()) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
160
179
|
} else if (payloadLength === 126) {
|
|
161
180
|
this.#state = parserStates.PAYLOADLENGTH_16
|
|
162
181
|
} else if (payloadLength === 127) {
|
|
@@ -181,6 +200,10 @@ class ByteParser extends Writable {
|
|
|
181
200
|
|
|
182
201
|
this.#info.payloadLength = buffer.readUInt16BE(0)
|
|
183
202
|
this.#state = parserStates.READ_DATA
|
|
203
|
+
|
|
204
|
+
if (!this.#validatePayloadLength()) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
184
207
|
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
|
185
208
|
if (this.#byteOffset < 8) {
|
|
186
209
|
return callback()
|
|
@@ -203,6 +226,10 @@ class ByteParser extends Writable {
|
|
|
203
226
|
|
|
204
227
|
this.#info.payloadLength = lower
|
|
205
228
|
this.#state = parserStates.READ_DATA
|
|
229
|
+
|
|
230
|
+
if (!this.#validatePayloadLength()) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
206
233
|
} else if (this.#state === parserStates.READ_DATA) {
|
|
207
234
|
if (this.#byteOffset < this.#info.payloadLength) {
|
|
208
235
|
return callback()
|
|
@@ -215,42 +242,53 @@ class ByteParser extends Writable {
|
|
|
215
242
|
this.#state = parserStates.INFO
|
|
216
243
|
} else {
|
|
217
244
|
if (!this.#info.compressed) {
|
|
218
|
-
this
|
|
245
|
+
this.writeFragments(body)
|
|
246
|
+
|
|
247
|
+
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
|
248
|
+
failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
|
|
249
|
+
return
|
|
250
|
+
}
|
|
219
251
|
|
|
220
252
|
// If the frame is not fragmented, a message has been received.
|
|
221
253
|
// If the frame is fragmented, it will terminate with a fin bit set
|
|
222
254
|
// and an opcode of 0 (continuation), therefore we handle that when
|
|
223
255
|
// parsing continuation frames, not here.
|
|
224
256
|
if (!this.#info.fragmented && this.#info.fin) {
|
|
225
|
-
|
|
226
|
-
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
|
227
|
-
this.#fragments.length = 0
|
|
257
|
+
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
|
228
258
|
}
|
|
229
259
|
|
|
230
260
|
this.#state = parserStates.INFO
|
|
231
261
|
} else {
|
|
232
|
-
this.#extensions.get('permessage-deflate').decompress(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
262
|
+
this.#extensions.get('permessage-deflate').decompress(
|
|
263
|
+
body,
|
|
264
|
+
this.#info.fin,
|
|
265
|
+
(error, data) => {
|
|
266
|
+
if (error) {
|
|
267
|
+
failWebsocketConnection(this.ws, error.message)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.writeFragments(data)
|
|
272
|
+
|
|
273
|
+
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
|
274
|
+
failWebsocketConnection(this.ws, new MessageSizeExceededError().message)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!this.#info.fin) {
|
|
279
|
+
this.#state = parserStates.INFO
|
|
280
|
+
this.#loop = true
|
|
281
|
+
this.run(callback)
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
|
237
286
|
|
|
238
|
-
this.#fragments.push(data)
|
|
239
|
-
|
|
240
|
-
if (!this.#info.fin) {
|
|
241
|
-
this.#state = parserStates.INFO
|
|
242
287
|
this.#loop = true
|
|
288
|
+
this.#state = parserStates.INFO
|
|
243
289
|
this.run(callback)
|
|
244
|
-
return
|
|
245
290
|
}
|
|
246
|
-
|
|
247
|
-
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
|
248
|
-
|
|
249
|
-
this.#loop = true
|
|
250
|
-
this.#state = parserStates.INFO
|
|
251
|
-
this.#fragments.length = 0
|
|
252
|
-
this.run(callback)
|
|
253
|
-
})
|
|
291
|
+
)
|
|
254
292
|
|
|
255
293
|
this.#loop = false
|
|
256
294
|
break
|
|
@@ -302,6 +340,26 @@ class ByteParser extends Writable {
|
|
|
302
340
|
return buffer
|
|
303
341
|
}
|
|
304
342
|
|
|
343
|
+
writeFragments (fragment) {
|
|
344
|
+
this.#fragmentsBytes += fragment.length
|
|
345
|
+
this.#fragments.push(fragment)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
consumeFragments () {
|
|
349
|
+
const fragments = this.#fragments
|
|
350
|
+
|
|
351
|
+
if (fragments.length === 1) {
|
|
352
|
+
this.#fragmentsBytes = 0
|
|
353
|
+
return fragments.shift()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
|
357
|
+
this.#fragments = []
|
|
358
|
+
this.#fragmentsBytes = 0
|
|
359
|
+
|
|
360
|
+
return output
|
|
361
|
+
}
|
|
362
|
+
|
|
305
363
|
parseCloseBody (data) {
|
|
306
364
|
assert(data.length !== 1)
|
|
307
365
|
|
|
@@ -44,9 +44,6 @@ class WebSocket extends EventTarget {
|
|
|
44
44
|
/** @type {SendQueue} */
|
|
45
45
|
#sendQueue
|
|
46
46
|
|
|
47
|
-
/** @type {{ maxDecompressedMessageSize?: number }} */
|
|
48
|
-
#options
|
|
49
|
-
|
|
50
47
|
/**
|
|
51
48
|
* @param {string} url
|
|
52
49
|
* @param {string|string[]} protocols
|
|
@@ -120,11 +117,6 @@ class WebSocket extends EventTarget {
|
|
|
120
117
|
// 10. Set this's url to urlRecord.
|
|
121
118
|
this[kWebSocketURL] = new URL(urlRecord.href)
|
|
122
119
|
|
|
123
|
-
// Store options for later use (e.g., maxDecompressedMessageSize)
|
|
124
|
-
this.#options = {
|
|
125
|
-
maxDecompressedMessageSize: options.maxDecompressedMessageSize
|
|
126
|
-
}
|
|
127
|
-
|
|
128
120
|
// 11. Let client be this's relevant settings object.
|
|
129
121
|
const client = environmentSettingsObject.settingsObject
|
|
130
122
|
|
|
@@ -443,7 +435,11 @@ class WebSocket extends EventTarget {
|
|
|
443
435
|
// once this happens, the connection is open
|
|
444
436
|
this[kResponse] = response
|
|
445
437
|
|
|
446
|
-
const
|
|
438
|
+
const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize
|
|
439
|
+
|
|
440
|
+
const parser = new ByteParser(this, parsedExtensions, {
|
|
441
|
+
maxPayloadSize
|
|
442
|
+
})
|
|
447
443
|
parser.on('drain', onParserDrain)
|
|
448
444
|
parser.on('error', onParserError.bind(this))
|
|
449
445
|
|
|
@@ -546,19 +542,6 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
|
|
546
542
|
{
|
|
547
543
|
key: 'headers',
|
|
548
544
|
converter: webidl.nullableConverter(webidl.converters.HeadersInit)
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
key: 'maxDecompressedMessageSize',
|
|
552
|
-
converter: webidl.nullableConverter((V) => {
|
|
553
|
-
V = webidl.converters['unsigned long long'](V)
|
|
554
|
-
if (V <= 0) {
|
|
555
|
-
throw webidl.errors.exception({
|
|
556
|
-
header: 'WebSocket constructor',
|
|
557
|
-
message: 'maxDecompressedMessageSize must be greater than 0'
|
|
558
|
-
})
|
|
559
|
-
}
|
|
560
|
-
return V
|
|
561
|
-
})
|
|
562
545
|
}
|
|
563
546
|
])
|
|
564
547
|
|
package/package.json
CHANGED
package/types/client.d.ts
CHANGED
|
@@ -78,6 +78,8 @@ export declare namespace Client {
|
|
|
78
78
|
localAddress?: string;
|
|
79
79
|
/** Max response body size in bytes, -1 is disabled */
|
|
80
80
|
maxResponseSize?: number;
|
|
81
|
+
/** WebSocket-specific options */
|
|
82
|
+
webSocket?: Client.WebSocketOptions;
|
|
81
83
|
/** Enables a family autodetection algorithm that loosely implements section 5 of RFC 8305. */
|
|
82
84
|
autoSelectFamily?: boolean;
|
|
83
85
|
/** The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. */
|
|
@@ -103,6 +105,15 @@ export declare namespace Client {
|
|
|
103
105
|
bytesWritten?: number
|
|
104
106
|
bytesRead?: number
|
|
105
107
|
}
|
|
108
|
+
export interface WebSocketOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Maximum allowed payload size in bytes for WebSocket messages.
|
|
111
|
+
* Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages.
|
|
112
|
+
* Set to 0 to disable the limit.
|
|
113
|
+
* @default 134217728 (128 MB)
|
|
114
|
+
*/
|
|
115
|
+
maxPayloadSize?: number;
|
|
116
|
+
}
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
export default Client;
|
package/types/websocket.d.ts
CHANGED
|
@@ -146,12 +146,5 @@ export declare const ErrorEvent: {
|
|
|
146
146
|
interface WebSocketInit {
|
|
147
147
|
protocols?: string | string[],
|
|
148
148
|
dispatcher?: Dispatcher,
|
|
149
|
-
headers?: HeadersInit
|
|
150
|
-
/**
|
|
151
|
-
* Maximum size in bytes for decompressed WebSocket messages.
|
|
152
|
-
* When a message exceeds this limit during decompression, the connection
|
|
153
|
-
* will be closed with status code 1009 (Message Too Big).
|
|
154
|
-
* @default 4194304 (4 MB)
|
|
155
|
-
*/
|
|
156
|
-
maxDecompressedMessageSize?: number
|
|
149
|
+
headers?: HeadersInit
|
|
157
150
|
}
|