undici 6.21.0 → 7.0.0-alpha.10
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 +27 -46
- package/docs/docs/api/Agent.md +14 -17
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +131 -0
- package/docs/docs/api/Client.md +12 -14
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +98 -194
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
- package/docs/docs/api/MockAgent.md +5 -3
- package/docs/docs/api/MockClient.md +5 -5
- package/docs/docs/api/MockPool.md +4 -3
- package/docs/docs/api/Pool.md +15 -16
- package/docs/docs/api/PoolStats.md +1 -1
- package/docs/docs/api/ProxyAgent.md +3 -3
- package/docs/docs/api/RedirectHandler.md +1 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +4 -4
- package/docs/docs/api/WebSocket.md +46 -4
- package/docs/docs/api/api-lifecycle.md +11 -11
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +23 -7
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +7 -6
- package/lib/api/api-request.js +33 -48
- package/lib/api/api-stream.js +39 -50
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +235 -62
- package/lib/api/util.js +2 -0
- package/lib/cache/memory-cache-store.js +177 -0
- package/lib/cache/sqlite-cache-store.js +446 -0
- package/lib/core/constants.js +35 -10
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +6 -6
- package/lib/core/request.js +13 -11
- package/lib/core/symbols.js +2 -1
- package/lib/core/tree.js +9 -1
- package/lib/core/util.js +237 -49
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +379 -134
- package/lib/dispatcher/client-h2.js +173 -107
- package/lib/dispatcher/client.js +19 -32
- package/lib/dispatcher/dispatcher-base.js +6 -35
- package/lib/dispatcher/dispatcher.js +7 -24
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +3 -6
- package/lib/handler/cache-handler.js +393 -0
- package/lib/handler/cache-revalidation-handler.js +124 -0
- package/lib/handler/decorator-handler.js +27 -0
- package/lib/handler/redirect-handler.js +54 -59
- package/lib/handler/retry-handler.js +77 -109
- package/lib/handler/unwrap-handler.js +96 -0
- package/lib/handler/wrap-handler.js +98 -0
- package/lib/interceptor/cache.js +350 -0
- package/lib/interceptor/dns.js +375 -0
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +18 -7
- package/lib/llhttp/constants.d.ts +97 -0
- package/lib/llhttp/constants.js +412 -192
- package/lib/llhttp/constants.js.map +1 -0
- package/lib/llhttp/llhttp-wasm.js +11 -1
- package/lib/llhttp/llhttp_simd-wasm.js +11 -1
- package/lib/llhttp/utils.d.ts +2 -0
- package/lib/llhttp/utils.js +9 -9
- package/lib/llhttp/utils.js.map +1 -0
- package/lib/mock/mock-agent.js +5 -8
- package/lib/mock/mock-client.js +9 -4
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +9 -4
- package/lib/mock/mock-symbols.js +3 -1
- package/lib/mock/mock-utils.js +29 -5
- package/lib/util/cache.js +360 -0
- package/lib/web/cache/cache.js +24 -21
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +29 -14
- package/lib/web/cookies/parse.js +8 -3
- package/lib/web/eventsource/eventsource-stream.js +9 -8
- package/lib/web/eventsource/eventsource.js +10 -6
- package/lib/web/fetch/body.js +43 -41
- package/lib/web/fetch/constants.js +12 -5
- package/lib/web/fetch/data-url.js +3 -3
- package/lib/web/fetch/formdata-parser.js +72 -45
- package/lib/web/fetch/formdata.js +65 -54
- package/lib/web/fetch/headers.js +118 -86
- package/lib/web/fetch/index.js +58 -67
- package/lib/web/fetch/request.js +136 -77
- package/lib/web/fetch/response.js +87 -56
- package/lib/web/fetch/util.js +259 -109
- package/lib/web/fetch/webidl.js +113 -68
- package/lib/web/websocket/connection.js +76 -147
- package/lib/web/websocket/constants.js +70 -10
- package/lib/web/websocket/events.js +4 -2
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +29 -33
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +128 -77
- package/lib/web/websocket/websocket.js +234 -135
- package/package.json +24 -36
- package/scripts/strip-comments.js +3 -1
- package/types/agent.d.ts +7 -7
- package/types/api.d.ts +24 -24
- package/types/balanced-pool.d.ts +11 -11
- package/types/cache-interceptor.d.ts +172 -0
- package/types/client.d.ts +11 -12
- package/types/cookies.d.ts +2 -0
- package/types/diagnostics-channel.d.ts +10 -10
- package/types/dispatcher.d.ts +113 -90
- package/types/env-http-proxy-agent.d.ts +2 -2
- package/types/errors.d.ts +53 -47
- package/types/fetch.d.ts +17 -16
- package/types/formdata.d.ts +7 -7
- package/types/global-dispatcher.d.ts +4 -4
- package/types/global-origin.d.ts +5 -5
- package/types/handlers.d.ts +7 -7
- package/types/header.d.ts +157 -1
- package/types/index.d.ts +44 -46
- package/types/interceptors.d.ts +25 -8
- package/types/mock-agent.d.ts +21 -18
- package/types/mock-client.d.ts +4 -4
- package/types/mock-errors.d.ts +3 -3
- package/types/mock-interceptor.d.ts +19 -19
- package/types/mock-pool.d.ts +4 -4
- package/types/patch.d.ts +0 -4
- package/types/pool-stats.d.ts +8 -8
- package/types/pool.d.ts +12 -12
- package/types/proxy-agent.d.ts +4 -4
- package/types/readable.d.ts +18 -15
- package/types/retry-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +10 -10
- package/types/util.d.ts +3 -3
- package/types/utility.d.ts +7 -0
- package/types/webidl.d.ts +44 -6
- package/types/websocket.d.ts +34 -1
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/file.js +0 -126
- package/lib/web/fetch/symbols.js +0 -9
- package/lib/web/fileapi/encoding.js +0 -290
- package/lib/web/fileapi/filereader.js +0 -344
- package/lib/web/fileapi/progressevent.js +0 -78
- package/lib/web/fileapi/symbols.js +0 -10
- package/lib/web/fileapi/util.js +0 -391
- package/lib/web/websocket/symbols.js +0 -12
- package/types/file.d.ts +0 -39
- package/types/filereader.d.ts +0 -54
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
|
|
4
|
-
const {
|
|
5
|
-
kReadyState,
|
|
6
|
-
kSentClose,
|
|
7
|
-
kByteParser,
|
|
8
|
-
kReceivedClose,
|
|
9
|
-
kResponse
|
|
10
|
-
} = require('./symbols')
|
|
11
|
-
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = require('./util')
|
|
4
|
+
const { failWebsocketConnection, parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
|
|
12
5
|
const { channels } = require('../../core/diagnostics')
|
|
13
|
-
const { CloseEvent } = require('./events')
|
|
14
6
|
const { makeRequest } = require('../fetch/request')
|
|
15
7
|
const { fetching } = require('../fetch/index')
|
|
16
8
|
const { Headers, getHeadersList } = require('../fetch/headers')
|
|
17
9
|
const { getDecodeSplit } = require('../fetch/util')
|
|
18
10
|
const { WebsocketFrameSend } = require('./frame')
|
|
11
|
+
const assert = require('node:assert')
|
|
19
12
|
|
|
20
13
|
/** @type {import('crypto')} */
|
|
21
14
|
let crypto
|
|
@@ -30,11 +23,10 @@ try {
|
|
|
30
23
|
* @see https://websockets.spec.whatwg.org/#concept-websocket-establish
|
|
31
24
|
* @param {URL} url
|
|
32
25
|
* @param {string|string[]} protocols
|
|
33
|
-
* @param {import('./websocket').
|
|
34
|
-
* @param {(
|
|
35
|
-
* @param {Partial<import('../../types/websocket').WebSocketInit>} options
|
|
26
|
+
* @param {import('./websocket').Handler} handler
|
|
27
|
+
* @param {Partial<import('../../../types/websocket').WebSocketInit>} options
|
|
36
28
|
*/
|
|
37
|
-
function establishWebSocketConnection (url, protocols, client,
|
|
29
|
+
function establishWebSocketConnection (url, protocols, client, handler, options) {
|
|
38
30
|
// 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s
|
|
39
31
|
// scheme is "ws", and to "https" otherwise.
|
|
40
32
|
const requestURL = url
|
|
@@ -75,17 +67,17 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
75
67
|
|
|
76
68
|
// 6. Append (`Sec-WebSocket-Key`, keyValue) to request’s
|
|
77
69
|
// header list.
|
|
78
|
-
request.headersList.append('sec-websocket-key', keyValue)
|
|
70
|
+
request.headersList.append('sec-websocket-key', keyValue, true)
|
|
79
71
|
|
|
80
72
|
// 7. Append (`Sec-WebSocket-Version`, `13`) to request’s
|
|
81
73
|
// header list.
|
|
82
|
-
request.headersList.append('sec-websocket-version', '13')
|
|
74
|
+
request.headersList.append('sec-websocket-version', '13', true)
|
|
83
75
|
|
|
84
76
|
// 8. For each protocol in protocols, combine
|
|
85
77
|
// (`Sec-WebSocket-Protocol`, protocol) in request’s header
|
|
86
78
|
// list.
|
|
87
79
|
for (const protocol of protocols) {
|
|
88
|
-
request.headersList.append('sec-websocket-protocol', protocol)
|
|
80
|
+
request.headersList.append('sec-websocket-protocol', protocol, true)
|
|
89
81
|
}
|
|
90
82
|
|
|
91
83
|
// 9. Let permessageDeflate be a user-agent defined
|
|
@@ -95,7 +87,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
95
87
|
|
|
96
88
|
// 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
|
|
97
89
|
// request’s header list.
|
|
98
|
-
request.headersList.append('sec-websocket-extensions', permessageDeflate)
|
|
90
|
+
request.headersList.append('sec-websocket-extensions', permessageDeflate, true)
|
|
99
91
|
|
|
100
92
|
// 11. Fetch request with useParallelQueue set to true, and
|
|
101
93
|
// processResponse given response being these steps:
|
|
@@ -104,10 +96,16 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
104
96
|
useParallelQueue: true,
|
|
105
97
|
dispatcher: options.dispatcher,
|
|
106
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
|
+
|
|
107
105
|
// 1. If response is a network error or its status is not 101,
|
|
108
106
|
// fail the WebSocket connection.
|
|
109
107
|
if (response.type === 'error' || response.status !== 101) {
|
|
110
|
-
failWebsocketConnection(
|
|
108
|
+
failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.')
|
|
111
109
|
return
|
|
112
110
|
}
|
|
113
111
|
|
|
@@ -116,7 +114,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
116
114
|
// header list results in null, failure, or the empty byte
|
|
117
115
|
// sequence, then fail the WebSocket connection.
|
|
118
116
|
if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
|
|
119
|
-
failWebsocketConnection(
|
|
117
|
+
failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
|
|
120
118
|
return
|
|
121
119
|
}
|
|
122
120
|
|
|
@@ -131,7 +129,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
131
129
|
// insensitive match for the value "websocket", the client MUST
|
|
132
130
|
// _Fail the WebSocket Connection_.
|
|
133
131
|
if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
|
|
134
|
-
failWebsocketConnection(
|
|
132
|
+
failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
|
|
135
133
|
return
|
|
136
134
|
}
|
|
137
135
|
|
|
@@ -140,7 +138,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
140
138
|
// ASCII case-insensitive match for the value "Upgrade", the client
|
|
141
139
|
// MUST _Fail the WebSocket Connection_.
|
|
142
140
|
if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
|
|
143
|
-
failWebsocketConnection(
|
|
141
|
+
failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
|
|
144
142
|
return
|
|
145
143
|
}
|
|
146
144
|
|
|
@@ -154,7 +152,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
154
152
|
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
|
|
155
153
|
const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
|
|
156
154
|
if (secWSAccept !== digest) {
|
|
157
|
-
failWebsocketConnection(
|
|
155
|
+
failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
|
158
156
|
return
|
|
159
157
|
}
|
|
160
158
|
|
|
@@ -172,7 +170,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
172
170
|
extensions = parseExtensions(secExtension)
|
|
173
171
|
|
|
174
172
|
if (!extensions.has('permessage-deflate')) {
|
|
175
|
-
failWebsocketConnection(
|
|
173
|
+
failWebsocketConnection(handler, 1002, 'Sec-WebSocket-Extensions header does not match.')
|
|
176
174
|
return
|
|
177
175
|
}
|
|
178
176
|
}
|
|
@@ -193,14 +191,14 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
193
191
|
// the selected subprotocol values in its response for the connection to
|
|
194
192
|
// be established.
|
|
195
193
|
if (!requestProtocols.includes(secProtocol)) {
|
|
196
|
-
failWebsocketConnection(
|
|
194
|
+
failWebsocketConnection(handler, 1002, 'Protocol was not set in the opening handshake.')
|
|
197
195
|
return
|
|
198
196
|
}
|
|
199
197
|
}
|
|
200
198
|
|
|
201
|
-
response.socket.on('data', onSocketData)
|
|
202
|
-
response.socket.on('close', onSocketClose)
|
|
203
|
-
response.socket.on('error', onSocketError)
|
|
199
|
+
response.socket.on('data', handler.onSocketData)
|
|
200
|
+
response.socket.on('close', handler.onSocketClose)
|
|
201
|
+
response.socket.on('error', handler.onSocketError)
|
|
204
202
|
|
|
205
203
|
if (channels.open.hasSubscribers) {
|
|
206
204
|
channels.open.publish({
|
|
@@ -210,35 +208,45 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
210
208
|
})
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
|
|
211
|
+
handler.wasEverConnected = true
|
|
212
|
+
handler.onConnectionEstablished(response, extensions)
|
|
214
213
|
}
|
|
215
214
|
})
|
|
216
215
|
|
|
217
216
|
return controller
|
|
218
217
|
}
|
|
219
218
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
/**
|
|
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='']
|
|
224
|
+
*/
|
|
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)) {
|
|
223
241
|
// Do nothing.
|
|
224
|
-
} else if (!isEstablished(
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
//
|
|
232
|
-
// Start the WebSocket closing handshake and set this's ready
|
|
233
|
-
// state to CLOSING (2).
|
|
234
|
-
// - If neither code nor reason is present, the WebSocket Close
|
|
235
|
-
// message must not have a body.
|
|
236
|
-
// - If code is present, then the status code to use in the
|
|
237
|
-
// WebSocket Close message must be the integer given by code.
|
|
238
|
-
// - If reason is also present, then reasonBytes must be
|
|
239
|
-
// provided in the Close message after the status code.
|
|
240
|
-
|
|
241
|
-
ws[kSentClose] = sentCloseFrameState.PROCESSING
|
|
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.
|
|
242
250
|
|
|
243
251
|
const frame = new WebsocketFrameSend()
|
|
244
252
|
|
|
@@ -247,13 +255,24 @@ function closeWebSocketConnection (ws, code, reason, reasonByteLength) {
|
|
|
247
255
|
|
|
248
256
|
// If code is present, then the status code to use in the
|
|
249
257
|
// WebSocket Close message must be the integer given by code.
|
|
250
|
-
|
|
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) {
|
|
251
270
|
frame.frameData = Buffer.allocUnsafe(2)
|
|
252
271
|
frame.frameData.writeUInt16BE(code, 0)
|
|
253
|
-
} else if (code !==
|
|
272
|
+
} else if (code !== null && reason !== null) {
|
|
254
273
|
// If reason is also present, then reasonBytes must be
|
|
255
274
|
// provided in the Close message after the status code.
|
|
256
|
-
frame.frameData = Buffer.allocUnsafe(2 +
|
|
275
|
+
frame.frameData = Buffer.allocUnsafe(2 + Buffer.byteLength(reason))
|
|
257
276
|
frame.frameData.writeUInt16BE(code, 0)
|
|
258
277
|
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
259
278
|
frame.frameData.write(reason, 2, 'utf-8')
|
|
@@ -261,108 +280,18 @@ function closeWebSocketConnection (ws, code, reason, reasonByteLength) {
|
|
|
261
280
|
frame.frameData = emptyBuffer
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
|
|
265
|
-
const socket = ws[kResponse].socket
|
|
266
|
-
|
|
267
|
-
socket.write(frame.createFrame(opcodes.CLOSE))
|
|
283
|
+
object.socket.write(frame.createFrame(opcodes.CLOSE))
|
|
268
284
|
|
|
269
|
-
|
|
285
|
+
object.closeState.add(sentCloseFrameState.SENT)
|
|
270
286
|
|
|
271
287
|
// Upon either sending or receiving a Close control frame, it is said
|
|
272
288
|
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
273
289
|
// WebSocket connection is in the CLOSING state.
|
|
274
|
-
|
|
290
|
+
object.readyState = states.CLOSING
|
|
275
291
|
} else {
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
ws[kReadyState] = states.CLOSING
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* @param {Buffer} chunk
|
|
284
|
-
*/
|
|
285
|
-
function onSocketData (chunk) {
|
|
286
|
-
if (!this.ws[kByteParser].write(chunk)) {
|
|
287
|
-
this.pause()
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|
293
|
-
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
|
|
294
|
-
*/
|
|
295
|
-
function onSocketClose () {
|
|
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)
|
|
302
|
-
|
|
303
|
-
// If the TCP connection was closed after the
|
|
304
|
-
// WebSocket closing handshake was completed, the WebSocket connection
|
|
305
|
-
// is said to have been closed _cleanly_.
|
|
306
|
-
const wasClean = ws[kSentClose] === sentCloseFrameState.SENT && ws[kReceivedClose]
|
|
307
|
-
|
|
308
|
-
let code = 1005
|
|
309
|
-
let reason = ''
|
|
310
|
-
|
|
311
|
-
const result = ws[kByteParser].closingInfo
|
|
312
|
-
|
|
313
|
-
if (result && !result.error) {
|
|
314
|
-
code = result.code ?? 1005
|
|
315
|
-
reason = result.reason
|
|
316
|
-
} else if (!ws[kReceivedClose]) {
|
|
317
|
-
// If _The WebSocket
|
|
318
|
-
// Connection is Closed_ and no Close control frame was received by the
|
|
319
|
-
// endpoint (such as could occur if the underlying transport connection
|
|
320
|
-
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
|
321
|
-
// 1006.
|
|
322
|
-
code = 1006
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// 1. Change the ready state to CLOSED (3).
|
|
326
|
-
ws[kReadyState] = states.CLOSED
|
|
327
|
-
|
|
328
|
-
// 2. If the user agent was required to fail the WebSocket
|
|
329
|
-
// connection, or if the WebSocket connection was closed
|
|
330
|
-
// after being flagged as full, fire an event named error
|
|
331
|
-
// at the WebSocket object.
|
|
332
|
-
// TODO
|
|
333
|
-
|
|
334
|
-
// 3. Fire an event named close at the WebSocket object,
|
|
335
|
-
// using CloseEvent, with the wasClean attribute
|
|
336
|
-
// initialized to true if the connection closed cleanly
|
|
337
|
-
// and false otherwise, the code attribute initialized to
|
|
338
|
-
// the WebSocket connection close code, and the reason
|
|
339
|
-
// attribute initialized to the result of applying UTF-8
|
|
340
|
-
// decode without BOM to the WebSocket connection close
|
|
341
|
-
// reason.
|
|
342
|
-
// TODO: process.nextTick
|
|
343
|
-
fireEvent('close', ws, (type, init) => new CloseEvent(type, init), {
|
|
344
|
-
wasClean, code, reason
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
if (channels.close.hasSubscribers) {
|
|
348
|
-
channels.close.publish({
|
|
349
|
-
websocket: ws,
|
|
350
|
-
code,
|
|
351
|
-
reason
|
|
352
|
-
})
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function onSocketError (error) {
|
|
357
|
-
const { ws } = this
|
|
358
|
-
|
|
359
|
-
ws[kReadyState] = states.CLOSING
|
|
360
|
-
|
|
361
|
-
if (channels.socketError.hasSubscribers) {
|
|
362
|
-
channels.socketError.publish(error)
|
|
292
|
+
// Set object’s ready state to CLOSING (2).
|
|
293
|
+
object.readyState = states.CLOSING
|
|
363
294
|
}
|
|
364
|
-
|
|
365
|
-
this.destroy()
|
|
366
295
|
}
|
|
367
296
|
|
|
368
297
|
module.exports = {
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* This is a Globally Unique Identifier unique used to validate that the
|
|
5
|
+
* endpoint accepts websocket connections.
|
|
6
|
+
* @see https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3
|
|
7
|
+
* @type {'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'}
|
|
8
|
+
*/
|
|
7
9
|
const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
|
8
10
|
|
|
9
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* @type {PropertyDescriptor}
|
|
13
|
+
*/
|
|
10
14
|
const staticPropertyDescriptors = {
|
|
11
15
|
enumerable: true,
|
|
12
16
|
writable: false,
|
|
13
17
|
configurable: false
|
|
14
18
|
}
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The states of the WebSocket connection.
|
|
22
|
+
*
|
|
23
|
+
* @readonly
|
|
24
|
+
* @enum
|
|
25
|
+
* @property {0} CONNECTING
|
|
26
|
+
* @property {1} OPEN
|
|
27
|
+
* @property {2} CLOSING
|
|
28
|
+
* @property {3} CLOSED
|
|
29
|
+
*/
|
|
16
30
|
const states = {
|
|
17
31
|
CONNECTING: 0,
|
|
18
32
|
OPEN: 1,
|
|
@@ -20,12 +34,31 @@ const states = {
|
|
|
20
34
|
CLOSED: 3
|
|
21
35
|
}
|
|
22
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @readonly
|
|
39
|
+
* @enum
|
|
40
|
+
* @property {0} NOT_SENT
|
|
41
|
+
* @property {1} PROCESSING
|
|
42
|
+
* @property {2} SENT
|
|
43
|
+
*/
|
|
23
44
|
const sentCloseFrameState = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
SENT: 2
|
|
45
|
+
SENT: 1,
|
|
46
|
+
RECEIVED: 2
|
|
27
47
|
}
|
|
28
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The WebSocket opcodes.
|
|
51
|
+
*
|
|
52
|
+
* @readonly
|
|
53
|
+
* @enum
|
|
54
|
+
* @property {0x0} CONTINUATION
|
|
55
|
+
* @property {0x1} TEXT
|
|
56
|
+
* @property {0x2} BINARY
|
|
57
|
+
* @property {0x8} CLOSE
|
|
58
|
+
* @property {0x9} PING
|
|
59
|
+
* @property {0xA} PONG
|
|
60
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
|
61
|
+
*/
|
|
29
62
|
const opcodes = {
|
|
30
63
|
CONTINUATION: 0x0,
|
|
31
64
|
TEXT: 0x1,
|
|
@@ -35,8 +68,23 @@ const opcodes = {
|
|
|
35
68
|
PONG: 0xA
|
|
36
69
|
}
|
|
37
70
|
|
|
38
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The maximum value for an unsigned 16-bit integer.
|
|
73
|
+
*
|
|
74
|
+
* @type {65535} 2 ** 16 - 1
|
|
75
|
+
*/
|
|
76
|
+
const maxUnsigned16Bit = 65535
|
|
39
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The states of the parser.
|
|
80
|
+
*
|
|
81
|
+
* @readonly
|
|
82
|
+
* @enum
|
|
83
|
+
* @property {0} INFO
|
|
84
|
+
* @property {2} PAYLOADLENGTH_16
|
|
85
|
+
* @property {3} PAYLOADLENGTH_64
|
|
86
|
+
* @property {4} READ_DATA
|
|
87
|
+
*/
|
|
40
88
|
const parserStates = {
|
|
41
89
|
INFO: 0,
|
|
42
90
|
PAYLOADLENGTH_16: 2,
|
|
@@ -44,10 +92,22 @@ const parserStates = {
|
|
|
44
92
|
READ_DATA: 4
|
|
45
93
|
}
|
|
46
94
|
|
|
95
|
+
/**
|
|
96
|
+
* An empty buffer.
|
|
97
|
+
*
|
|
98
|
+
* @type {Buffer}
|
|
99
|
+
*/
|
|
47
100
|
const emptyBuffer = Buffer.allocUnsafe(0)
|
|
48
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @readonly
|
|
104
|
+
* @property {1} text
|
|
105
|
+
* @property {2} typedArray
|
|
106
|
+
* @property {3} arrayBuffer
|
|
107
|
+
* @property {4} blob
|
|
108
|
+
*/
|
|
49
109
|
const sendHints = {
|
|
50
|
-
|
|
110
|
+
text: 1,
|
|
51
111
|
typedArray: 2,
|
|
52
112
|
arrayBuffer: 3,
|
|
53
113
|
blob: 4
|
|
@@ -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
|
|
@@ -219,7 +218,10 @@ Object.defineProperties(ErrorEvent.prototype, {
|
|
|
219
218
|
error: kEnumerableProperty
|
|
220
219
|
})
|
|
221
220
|
|
|
222
|
-
webidl.converters.MessagePort = webidl.interfaceConverter(
|
|
221
|
+
webidl.converters.MessagePort = webidl.interfaceConverter(
|
|
222
|
+
webidl.is.MessagePort,
|
|
223
|
+
'MessagePort'
|
|
224
|
+
)
|
|
223
225
|
|
|
224
226
|
webidl.converters['sequence<MessagePort>'] = webidl.sequenceConverter(
|
|
225
227
|
webidl.converters.MessagePort
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { maxUnsigned16Bit } = require('./constants')
|
|
3
|
+
const { maxUnsigned16Bit, opcodes } = require('./constants')
|
|
4
4
|
|
|
5
|
-
const BUFFER_SIZE =
|
|
5
|
+
const BUFFER_SIZE = 8 * 1024
|
|
6
6
|
|
|
7
7
|
/** @type {import('crypto')} */
|
|
8
8
|
let crypto
|
|
@@ -27,7 +27,7 @@ try {
|
|
|
27
27
|
function generateMask () {
|
|
28
28
|
if (bufIdx === BUFFER_SIZE) {
|
|
29
29
|
bufIdx = 0
|
|
30
|
-
crypto.randomFillSync((buffer ??= Buffer.
|
|
30
|
+
crypto.randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
|
|
31
31
|
}
|
|
32
32
|
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
|
|
33
33
|
}
|
|
@@ -89,6 +89,48 @@ class WebsocketFrameSend {
|
|
|
89
89
|
|
|
90
90
|
return buffer
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Uint8Array} buffer
|
|
95
|
+
*/
|
|
96
|
+
static createFastTextFrame (buffer) {
|
|
97
|
+
const maskKey = generateMask()
|
|
98
|
+
|
|
99
|
+
const bodyLength = buffer.length
|
|
100
|
+
|
|
101
|
+
// mask body
|
|
102
|
+
for (let i = 0; i < bodyLength; ++i) {
|
|
103
|
+
buffer[i] ^= maskKey[i & 3]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let payloadLength = bodyLength
|
|
107
|
+
let offset = 6
|
|
108
|
+
|
|
109
|
+
if (bodyLength > maxUnsigned16Bit) {
|
|
110
|
+
offset += 8 // payload length is next 8 bytes
|
|
111
|
+
payloadLength = 127
|
|
112
|
+
} else if (bodyLength > 125) {
|
|
113
|
+
offset += 2 // payload length is next 2 bytes
|
|
114
|
+
payloadLength = 126
|
|
115
|
+
}
|
|
116
|
+
const head = Buffer.allocUnsafeSlow(offset)
|
|
117
|
+
|
|
118
|
+
head[0] = 0x80 /* FIN */ | opcodes.TEXT /* opcode TEXT */
|
|
119
|
+
head[1] = payloadLength | 0x80 /* MASK */
|
|
120
|
+
head[offset - 4] = maskKey[0]
|
|
121
|
+
head[offset - 3] = maskKey[1]
|
|
122
|
+
head[offset - 2] = maskKey[2]
|
|
123
|
+
head[offset - 1] = maskKey[3]
|
|
124
|
+
|
|
125
|
+
if (payloadLength === 126) {
|
|
126
|
+
head.writeUInt16BE(bodyLength, 2)
|
|
127
|
+
} else if (payloadLength === 127) {
|
|
128
|
+
head[2] = head[3] = 0
|
|
129
|
+
head.writeUIntBE(bodyLength, 4, 6)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [head, buffer]
|
|
133
|
+
}
|
|
92
134
|
}
|
|
93
135
|
|
|
94
136
|
module.exports = {
|