undici 7.0.0-alpha.1 → 7.0.0-alpha.2
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/README.md +2 -2
- package/docs/docs/api/Client.md +1 -1
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +53 -2
- package/docs/docs/api/MockAgent.md +2 -0
- package/docs/docs/api/MockPool.md +2 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/docs/docs/api/WebSocket.md +45 -3
- package/index.js +6 -2
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-pipeline.js +4 -2
- package/lib/api/api-request.js +4 -2
- package/lib/api/api-stream.js +3 -1
- package/lib/api/api-upgrade.js +2 -2
- package/lib/api/readable.js +194 -41
- package/lib/api/util.js +2 -0
- package/lib/core/connect.js +49 -22
- package/lib/core/constants.js +11 -9
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/request.js +4 -4
- package/lib/core/symbols.js +2 -0
- package/lib/core/tree.js +4 -2
- package/lib/core/util.js +220 -39
- package/lib/dispatcher/client-h1.js +299 -60
- package/lib/dispatcher/client-h2.js +1 -1
- package/lib/dispatcher/client.js +24 -7
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/proxy-agent.js +3 -1
- package/lib/handler/redirect-handler.js +2 -2
- package/lib/handler/retry-handler.js +2 -2
- package/lib/interceptor/dns.js +346 -0
- package/lib/mock/mock-agent.js +5 -8
- package/lib/mock/mock-client.js +7 -2
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +7 -2
- package/lib/mock/mock-symbols.js +2 -1
- package/lib/mock/mock-utils.js +33 -5
- package/lib/util/timers.js +50 -6
- package/lib/web/cache/cache.js +24 -21
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +6 -4
- package/lib/web/fetch/body.js +42 -34
- package/lib/web/fetch/constants.js +35 -26
- package/lib/web/fetch/formdata-parser.js +14 -3
- package/lib/web/fetch/formdata.js +40 -20
- package/lib/web/fetch/headers.js +116 -84
- package/lib/web/fetch/index.js +65 -59
- package/lib/web/fetch/request.js +130 -55
- package/lib/web/fetch/response.js +79 -36
- package/lib/web/fetch/util.js +104 -57
- package/lib/web/fetch/webidl.js +38 -14
- package/lib/web/websocket/connection.js +92 -15
- package/lib/web/websocket/constants.js +2 -3
- package/lib/web/websocket/events.js +4 -2
- package/lib/web/websocket/receiver.js +20 -26
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +115 -10
- package/lib/web/websocket/websocket.js +45 -170
- package/package.json +6 -6
- package/types/interceptors.d.ts +14 -0
- package/types/mock-agent.d.ts +3 -0
- package/types/readable.d.ts +10 -7
- package/types/webidl.d.ts +24 -4
- package/types/websocket.d.ts +33 -0
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/symbols.js +0 -8
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { uid } = require('./constants')
|
|
4
|
-
const { failWebsocketConnection, parseExtensions } = require('./util')
|
|
3
|
+
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
|
|
4
|
+
const { failWebsocketConnection, parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
|
|
5
5
|
const { channels } = require('../../core/diagnostics')
|
|
6
6
|
const { makeRequest } = require('../fetch/request')
|
|
7
7
|
const { fetching } = require('../fetch/index')
|
|
8
8
|
const { Headers, getHeadersList } = require('../fetch/headers')
|
|
9
9
|
const { getDecodeSplit } = require('../fetch/util')
|
|
10
|
+
const { WebsocketFrameSend } = require('./frame')
|
|
11
|
+
const assert = require('node:assert')
|
|
10
12
|
|
|
11
13
|
/** @type {import('crypto')} */
|
|
12
14
|
let crypto
|
|
@@ -94,10 +96,16 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
94
96
|
useParallelQueue: true,
|
|
95
97
|
dispatcher: options.dispatcher,
|
|
96
98
|
processResponse (response) {
|
|
99
|
+
if (response.type === 'error') {
|
|
100
|
+
// If the WebSocket connection could not be established, it is also said
|
|
101
|
+
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
|
102
|
+
handler.readyState = states.CLOSED
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
// 1. If response is a network error or its status is not 101,
|
|
98
106
|
// fail the WebSocket connection.
|
|
99
107
|
if (response.type === 'error' || response.status !== 101) {
|
|
100
|
-
failWebsocketConnection(handler, 'Received network error or non-101 status code.')
|
|
108
|
+
failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.')
|
|
101
109
|
return
|
|
102
110
|
}
|
|
103
111
|
|
|
@@ -106,7 +114,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
106
114
|
// header list results in null, failure, or the empty byte
|
|
107
115
|
// sequence, then fail the WebSocket connection.
|
|
108
116
|
if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
|
|
109
|
-
failWebsocketConnection(handler, 'Server did not respond with sent protocols.')
|
|
117
|
+
failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
|
|
110
118
|
return
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -121,7 +129,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
121
129
|
// insensitive match for the value "websocket", the client MUST
|
|
122
130
|
// _Fail the WebSocket Connection_.
|
|
123
131
|
if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
|
|
124
|
-
failWebsocketConnection(handler, 'Server did not set Upgrade header to "websocket".')
|
|
132
|
+
failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
|
|
125
133
|
return
|
|
126
134
|
}
|
|
127
135
|
|
|
@@ -130,7 +138,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
130
138
|
// ASCII case-insensitive match for the value "Upgrade", the client
|
|
131
139
|
// MUST _Fail the WebSocket Connection_.
|
|
132
140
|
if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
|
|
133
|
-
failWebsocketConnection(handler, 'Server did not set Connection header to "upgrade".')
|
|
141
|
+
failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
|
|
134
142
|
return
|
|
135
143
|
}
|
|
136
144
|
|
|
@@ -144,7 +152,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
144
152
|
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
|
|
145
153
|
const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
|
|
146
154
|
if (secWSAccept !== digest) {
|
|
147
|
-
failWebsocketConnection(handler, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
|
155
|
+
failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
|
148
156
|
return
|
|
149
157
|
}
|
|
150
158
|
|
|
@@ -162,7 +170,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
162
170
|
extensions = parseExtensions(secExtension)
|
|
163
171
|
|
|
164
172
|
if (!extensions.has('permessage-deflate')) {
|
|
165
|
-
failWebsocketConnection(handler, 'Sec-WebSocket-Extensions header does not match.')
|
|
173
|
+
failWebsocketConnection(handler, 1002, 'Sec-WebSocket-Extensions header does not match.')
|
|
166
174
|
return
|
|
167
175
|
}
|
|
168
176
|
}
|
|
@@ -183,7 +191,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
183
191
|
// the selected subprotocol values in its response for the connection to
|
|
184
192
|
// be established.
|
|
185
193
|
if (!requestProtocols.includes(secProtocol)) {
|
|
186
|
-
failWebsocketConnection(handler, 'Protocol was not set in the opening handshake.')
|
|
194
|
+
failWebsocketConnection(handler, 1002, 'Protocol was not set in the opening handshake.')
|
|
187
195
|
return
|
|
188
196
|
}
|
|
189
197
|
}
|
|
@@ -200,6 +208,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
200
208
|
})
|
|
201
209
|
}
|
|
202
210
|
|
|
211
|
+
handler.wasEverConnected = true
|
|
203
212
|
handler.onConnectionEstablished(response, extensions)
|
|
204
213
|
}
|
|
205
214
|
})
|
|
@@ -208,13 +217,81 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
/**
|
|
211
|
-
* @
|
|
212
|
-
* @param {
|
|
213
|
-
* @param {
|
|
214
|
-
* @param {
|
|
220
|
+
* @see https://whatpr.org/websockets/48.html#close-the-websocket
|
|
221
|
+
* @param {import('./websocket').Handler} object
|
|
222
|
+
* @param {number} [code=null]
|
|
223
|
+
* @param {string} [reason='']
|
|
215
224
|
*/
|
|
216
|
-
function closeWebSocketConnection (
|
|
217
|
-
|
|
225
|
+
function closeWebSocketConnection (object, code, reason, validate = false) {
|
|
226
|
+
// 1. If code was not supplied, let code be null.
|
|
227
|
+
code ??= null
|
|
228
|
+
|
|
229
|
+
// 2. If reason was not supplied, let reason be the empty string.
|
|
230
|
+
reason ??= ''
|
|
231
|
+
|
|
232
|
+
// 3. Validate close code and reason with code and reason.
|
|
233
|
+
if (validate) validateCloseCodeAndReason(code, reason)
|
|
234
|
+
|
|
235
|
+
// 4. Run the first matching steps from the following list:
|
|
236
|
+
// - If object’s ready state is CLOSING (2) or CLOSED (3)
|
|
237
|
+
// - If the WebSocket connection is not yet established [WSP]
|
|
238
|
+
// - If the WebSocket closing handshake has not yet been started [WSP]
|
|
239
|
+
// - Otherwise
|
|
240
|
+
if (isClosed(object.readyState) || isClosing(object.readyState)) {
|
|
241
|
+
// Do nothing.
|
|
242
|
+
} else if (!isEstablished(object.readyState)) {
|
|
243
|
+
// Fail the WebSocket connection and set object’s ready state to CLOSING (2). [WSP]
|
|
244
|
+
failWebsocketConnection(object)
|
|
245
|
+
object.readyState = states.CLOSING
|
|
246
|
+
} else if (!object.closeState.has(sentCloseFrameState.SENT) && !object.closeState.has(sentCloseFrameState.RECEIVED)) {
|
|
247
|
+
// Upon either sending or receiving a Close control frame, it is said
|
|
248
|
+
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
249
|
+
// WebSocket connection is in the CLOSING state.
|
|
250
|
+
|
|
251
|
+
const frame = new WebsocketFrameSend()
|
|
252
|
+
|
|
253
|
+
// If neither code nor reason is present, the WebSocket Close
|
|
254
|
+
// message must not have a body.
|
|
255
|
+
|
|
256
|
+
// If code is present, then the status code to use in the
|
|
257
|
+
// WebSocket Close message must be the integer given by code.
|
|
258
|
+
// If code is null and reason is the empty string, the WebSocket Close frame must not have a body.
|
|
259
|
+
// If reason is non-empty but code is null, then set code to 1000 ("Normal Closure").
|
|
260
|
+
if (reason.length !== 0 && code === null) {
|
|
261
|
+
code = 1000
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// If code is set, then the status code to use in the WebSocket Close frame must be the integer given by code.
|
|
265
|
+
assert(code === null || Number.isInteger(code))
|
|
266
|
+
|
|
267
|
+
if (code === null && reason.length === 0) {
|
|
268
|
+
frame.frameData = emptyBuffer
|
|
269
|
+
} else if (code !== null && reason === null) {
|
|
270
|
+
frame.frameData = Buffer.allocUnsafe(2)
|
|
271
|
+
frame.frameData.writeUInt16BE(code, 0)
|
|
272
|
+
} else if (code !== null && reason !== null) {
|
|
273
|
+
// If reason is also present, then reasonBytes must be
|
|
274
|
+
// provided in the Close message after the status code.
|
|
275
|
+
frame.frameData = Buffer.allocUnsafe(2 + Buffer.byteLength(reason))
|
|
276
|
+
frame.frameData.writeUInt16BE(code, 0)
|
|
277
|
+
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
278
|
+
frame.frameData.write(reason, 2, 'utf-8')
|
|
279
|
+
} else {
|
|
280
|
+
frame.frameData = emptyBuffer
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
object.socket.write(frame.createFrame(opcodes.CLOSE))
|
|
284
|
+
|
|
285
|
+
object.closeState.add(sentCloseFrameState.SENT)
|
|
286
|
+
|
|
287
|
+
// Upon either sending or receiving a Close control frame, it is said
|
|
288
|
+
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
289
|
+
// WebSocket connection is in the CLOSING state.
|
|
290
|
+
object.readyState = states.CLOSING
|
|
291
|
+
} else {
|
|
292
|
+
// Set object’s ready state to CLOSING (2).
|
|
293
|
+
object.readyState = states.CLOSING
|
|
294
|
+
}
|
|
218
295
|
}
|
|
219
296
|
|
|
220
297
|
module.exports = {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const { webidl } = require('../fetch/webidl')
|
|
4
4
|
const { kEnumerableProperty } = require('../../core/util')
|
|
5
5
|
const { kConstruct } = require('../../core/symbols')
|
|
6
|
-
const { MessagePort } = require('node:worker_threads')
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* @see https://html.spec.whatwg.org/multipage/comms.html#messageevent
|
|
@@ -215,7 +214,10 @@ Object.defineProperties(ErrorEvent.prototype, {
|
|
|
215
214
|
error: kEnumerableProperty
|
|
216
215
|
})
|
|
217
216
|
|
|
218
|
-
webidl.converters.MessagePort = webidl.interfaceConverter(
|
|
217
|
+
webidl.converters.MessagePort = webidl.interfaceConverter(
|
|
218
|
+
webidl.is.MessagePort,
|
|
219
|
+
'MessagePort'
|
|
220
|
+
)
|
|
219
221
|
|
|
220
222
|
webidl.converters['sequence<MessagePort>'] = webidl.sequenceConverter(
|
|
221
223
|
webidl.converters.MessagePort
|
|
@@ -15,7 +15,6 @@ const {
|
|
|
15
15
|
isContinuationFrame
|
|
16
16
|
} = require('./util')
|
|
17
17
|
const { WebsocketFrameSend } = require('./frame')
|
|
18
|
-
const { closeWebSocketConnection } = require('./connection')
|
|
19
18
|
const { PerMessageDeflate } = require('./permessage-deflate')
|
|
20
19
|
|
|
21
20
|
// This code was influenced by ws released under the MIT license.
|
|
@@ -88,12 +87,12 @@ class ByteParser extends Writable {
|
|
|
88
87
|
const rsv3 = buffer[0] & 0x10
|
|
89
88
|
|
|
90
89
|
if (!isValidOpcode(opcode)) {
|
|
91
|
-
failWebsocketConnection(this.#handler, 'Invalid opcode received')
|
|
90
|
+
failWebsocketConnection(this.#handler, 1002, 'Invalid opcode received')
|
|
92
91
|
return callback()
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
if (masked) {
|
|
96
|
-
failWebsocketConnection(this.#handler, 'Frame cannot be masked')
|
|
95
|
+
failWebsocketConnection(this.#handler, 1002, 'Frame cannot be masked')
|
|
97
96
|
return callback()
|
|
98
97
|
}
|
|
99
98
|
|
|
@@ -107,43 +106,43 @@ class ByteParser extends Writable {
|
|
|
107
106
|
// WebSocket connection where a PMCE is in use, this bit indicates
|
|
108
107
|
// whether a message is compressed or not.
|
|
109
108
|
if (rsv1 !== 0 && !this.#extensions.has('permessage-deflate')) {
|
|
110
|
-
failWebsocketConnection(this.#handler, 'Expected RSV1 to be clear.')
|
|
109
|
+
failWebsocketConnection(this.#handler, 1002, 'Expected RSV1 to be clear.')
|
|
111
110
|
return
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
if (rsv2 !== 0 || rsv3 !== 0) {
|
|
115
|
-
failWebsocketConnection(this.#handler, 'RSV1, RSV2, RSV3 must be clear')
|
|
114
|
+
failWebsocketConnection(this.#handler, 1002, 'RSV1, RSV2, RSV3 must be clear')
|
|
116
115
|
return
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
if (fragmented && !isTextBinaryFrame(opcode)) {
|
|
120
119
|
// Only text and binary frames can be fragmented
|
|
121
|
-
failWebsocketConnection(this.#handler, 'Invalid frame type was fragmented.')
|
|
120
|
+
failWebsocketConnection(this.#handler, 1002, 'Invalid frame type was fragmented.')
|
|
122
121
|
return
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
// If we are already parsing a text/binary frame and do not receive either
|
|
126
125
|
// a continuation frame or close frame, fail the connection.
|
|
127
126
|
if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) {
|
|
128
|
-
failWebsocketConnection(this.#handler, 'Expected continuation frame')
|
|
127
|
+
failWebsocketConnection(this.#handler, 1002, 'Expected continuation frame')
|
|
129
128
|
return
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
if (this.#info.fragmented && fragmented) {
|
|
133
132
|
// A fragmented frame can't be fragmented itself
|
|
134
|
-
failWebsocketConnection(this.#handler, 'Fragmented frame exceeded 125 bytes.')
|
|
133
|
+
failWebsocketConnection(this.#handler, 1002, 'Fragmented frame exceeded 125 bytes.')
|
|
135
134
|
return
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
// "All control frames MUST have a payload length of 125 bytes or less
|
|
139
138
|
// and MUST NOT be fragmented."
|
|
140
139
|
if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) {
|
|
141
|
-
failWebsocketConnection(this.#handler, 'Control frame either too large or fragmented')
|
|
140
|
+
failWebsocketConnection(this.#handler, 1002, 'Control frame either too large or fragmented')
|
|
142
141
|
return
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) {
|
|
146
|
-
failWebsocketConnection(this.#handler, 'Unexpected continuation frame')
|
|
145
|
+
failWebsocketConnection(this.#handler, 1002, 'Unexpected continuation frame')
|
|
147
146
|
return
|
|
148
147
|
}
|
|
149
148
|
|
|
@@ -189,7 +188,7 @@ class ByteParser extends Writable {
|
|
|
189
188
|
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
|
|
190
189
|
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
|
|
191
190
|
if (upper > 2 ** 31 - 1) {
|
|
192
|
-
failWebsocketConnection(this.#handler, 'Received payload length > 2^31 bytes.')
|
|
191
|
+
failWebsocketConnection(this.#handler, 1009, 'Received payload length > 2^31 bytes.')
|
|
193
192
|
return
|
|
194
193
|
}
|
|
195
194
|
|
|
@@ -225,7 +224,7 @@ class ByteParser extends Writable {
|
|
|
225
224
|
} else {
|
|
226
225
|
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
|
227
226
|
if (error) {
|
|
228
|
-
|
|
227
|
+
failWebsocketConnection(this.#handler, 1007, error.message)
|
|
229
228
|
return
|
|
230
229
|
}
|
|
231
230
|
|
|
@@ -341,7 +340,7 @@ class ByteParser extends Writable {
|
|
|
341
340
|
|
|
342
341
|
if (opcode === opcodes.CLOSE) {
|
|
343
342
|
if (payloadLength === 1) {
|
|
344
|
-
failWebsocketConnection(this.#handler, 'Received close frame with a 1-byte body.')
|
|
343
|
+
failWebsocketConnection(this.#handler, 1002, 'Received close frame with a 1-byte body.')
|
|
345
344
|
return false
|
|
346
345
|
}
|
|
347
346
|
|
|
@@ -350,12 +349,13 @@ class ByteParser extends Writable {
|
|
|
350
349
|
if (this.#info.closeInfo.error) {
|
|
351
350
|
const { code, reason } = this.#info.closeInfo
|
|
352
351
|
|
|
353
|
-
|
|
354
|
-
failWebsocketConnection(this.#handler, reason)
|
|
352
|
+
failWebsocketConnection(this.#handler, code, reason)
|
|
355
353
|
return false
|
|
356
354
|
}
|
|
357
355
|
|
|
358
|
-
|
|
356
|
+
// Upon receiving such a frame, the other peer sends a
|
|
357
|
+
// Close frame in response, if it hasn't already sent one.
|
|
358
|
+
if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
|
359
359
|
// If an endpoint receives a Close frame and did not previously send a
|
|
360
360
|
// Close frame, the endpoint MUST send a Close frame in response. (When
|
|
361
361
|
// sending a Close frame in response, the endpoint typically echos the
|
|
@@ -367,21 +367,15 @@ class ByteParser extends Writable {
|
|
|
367
367
|
}
|
|
368
368
|
const closeFrame = new WebsocketFrameSend(body)
|
|
369
369
|
|
|
370
|
-
this.#handler.socket.write(
|
|
371
|
-
|
|
372
|
-
(err) => {
|
|
373
|
-
if (!err) {
|
|
374
|
-
this.#handler.closeState = sentCloseFrameState.SENT
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
)
|
|
370
|
+
this.#handler.socket.write(closeFrame.createFrame(opcodes.CLOSE))
|
|
371
|
+
this.#handler.closeState.add(sentCloseFrameState.SENT)
|
|
378
372
|
}
|
|
379
373
|
|
|
380
374
|
// Upon either sending or receiving a Close control frame, it is said
|
|
381
375
|
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
382
376
|
// WebSocket connection is in the CLOSING state.
|
|
383
377
|
this.#handler.readyState = states.CLOSING
|
|
384
|
-
this.#handler.
|
|
378
|
+
this.#handler.closeState.add(sentCloseFrameState.RECEIVED)
|
|
385
379
|
|
|
386
380
|
return false
|
|
387
381
|
} else if (opcode === opcodes.PING) {
|
|
@@ -390,7 +384,7 @@ class ByteParser extends Writable {
|
|
|
390
384
|
// A Pong frame sent in response to a Ping frame must have identical
|
|
391
385
|
// "Application data"
|
|
392
386
|
|
|
393
|
-
if (!this.#handler.
|
|
387
|
+
if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
|
394
388
|
const frame = new WebsocketFrameSend(body)
|
|
395
389
|
|
|
396
390
|
this.#handler.socket.write(frame.createFrame(opcodes.PONG))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { webidl } = require('../../fetch/webidl')
|
|
4
|
+
const { validateCloseCodeAndReason } = require('../util')
|
|
5
|
+
const { kConstruct } = require('../../../core/symbols')
|
|
6
|
+
const { kEnumerableProperty } = require('../../../core/util')
|
|
7
|
+
|
|
8
|
+
class WebSocketError extends DOMException {
|
|
9
|
+
#closeCode
|
|
10
|
+
#reason
|
|
11
|
+
|
|
12
|
+
constructor (message = '', init = undefined) {
|
|
13
|
+
message = webidl.converters.DOMString(message, 'WebSocketError', 'message')
|
|
14
|
+
|
|
15
|
+
// 1. Set this 's name to " WebSocketError ".
|
|
16
|
+
// 2. Set this 's message to message .
|
|
17
|
+
super(message, 'WebSocketError')
|
|
18
|
+
|
|
19
|
+
if (init === kConstruct) {
|
|
20
|
+
return
|
|
21
|
+
} else if (init !== null) {
|
|
22
|
+
init = webidl.converters.WebSocketCloseInfo(init)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 3. Let code be init [" closeCode "] if it exists , or null otherwise.
|
|
26
|
+
let code = init.closeCode ?? null
|
|
27
|
+
|
|
28
|
+
// 4. Let reason be init [" reason "] if it exists , or the empty string otherwise.
|
|
29
|
+
const reason = init.reason ?? ''
|
|
30
|
+
|
|
31
|
+
// 5. Validate close code and reason with code and reason .
|
|
32
|
+
validateCloseCodeAndReason(code, reason)
|
|
33
|
+
|
|
34
|
+
// 6. If reason is non-empty, but code is not set, then set code to 1000 ("Normal Closure").
|
|
35
|
+
if (reason.length !== 0 && code === null) {
|
|
36
|
+
code = 1000
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 7. Set this 's closeCode to code .
|
|
40
|
+
this.#closeCode = code
|
|
41
|
+
|
|
42
|
+
// 8. Set this 's reason to reason .
|
|
43
|
+
this.#reason = reason
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get closeCode () {
|
|
47
|
+
return this.#closeCode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get reason () {
|
|
51
|
+
return this.#reason
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} message
|
|
56
|
+
* @param {number|null} code
|
|
57
|
+
* @param {string} reason
|
|
58
|
+
*/
|
|
59
|
+
static createUnvalidatedWebSocketError (message, code, reason) {
|
|
60
|
+
const error = new WebSocketError(message, kConstruct)
|
|
61
|
+
error.#closeCode = code
|
|
62
|
+
error.#reason = reason
|
|
63
|
+
return error
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { createUnvalidatedWebSocketError } = WebSocketError
|
|
68
|
+
delete WebSocketError.createUnvalidatedWebSocketError
|
|
69
|
+
|
|
70
|
+
Object.defineProperties(WebSocketError.prototype, {
|
|
71
|
+
closeCode: kEnumerableProperty,
|
|
72
|
+
reason: kEnumerableProperty,
|
|
73
|
+
[Symbol.toStringTag]: {
|
|
74
|
+
value: 'WebSocketError',
|
|
75
|
+
writable: false,
|
|
76
|
+
enumerable: false,
|
|
77
|
+
configurable: true
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
webidl.is.WebSocketError = webidl.util.MakeTypeAssertion(WebSocketError.prototype)
|
|
82
|
+
|
|
83
|
+
module.exports = { WebSocketError, createUnvalidatedWebSocketError }
|