undici 7.0.0-alpha.1 → 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 +24 -38
- package/docs/docs/api/Agent.md +14 -14
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +131 -0
- package/docs/docs/api/Client.md +12 -12
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +98 -193
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -12
- 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 -15
- 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 -3
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-pipeline.js +4 -2
- package/lib/api/api-request.js +6 -4
- package/lib/api/api-stream.js +3 -1
- package/lib/api/api-upgrade.js +2 -2
- package/lib/api/readable.js +200 -47
- 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/connect.js +54 -22
- package/lib/core/constants.js +35 -10
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +2 -2
- package/lib/core/request.js +6 -6
- package/lib/core/symbols.js +2 -0
- package/lib/core/tree.js +4 -2
- package/lib/core/util.js +238 -40
- package/lib/dispatcher/client-h1.js +405 -142
- package/lib/dispatcher/client-h2.js +212 -109
- package/lib/dispatcher/client.js +24 -7
- package/lib/dispatcher/dispatcher-base.js +4 -1
- package/lib/dispatcher/dispatcher.js +4 -0
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/proxy-agent.js +3 -1
- package/lib/handler/cache-handler.js +393 -0
- package/lib/handler/cache-revalidation-handler.js +124 -0
- package/lib/handler/decorator-handler.js +3 -0
- package/lib/handler/redirect-handler.js +45 -59
- package/lib/handler/retry-handler.js +68 -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/response-error.js +15 -7
- 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/cache.js +360 -0
- package/lib/util/timers.js +50 -6
- package/lib/web/cache/cache.js +25 -21
- package/lib/web/cache/cachestorage.js +3 -1
- package/lib/web/cookies/index.js +18 -5
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/body.js +43 -39
- package/lib/web/fetch/constants.js +45 -29
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata-parser.js +84 -46
- package/lib/web/fetch/formdata.js +42 -20
- package/lib/web/fetch/headers.js +119 -85
- package/lib/web/fetch/index.js +69 -65
- package/lib/web/fetch/request.js +132 -55
- package/lib/web/fetch/response.js +81 -36
- package/lib/web/fetch/util.js +274 -103
- package/lib/web/fetch/webidl.js +54 -18
- package/lib/web/websocket/connection.js +92 -15
- package/lib/web/websocket/constants.js +69 -9
- package/lib/web/websocket/events.js +8 -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 +47 -170
- package/package.json +15 -11
- package/types/agent.d.ts +1 -1
- package/types/cache-interceptor.d.ts +172 -0
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +29 -4
- package/types/env-http-proxy-agent.d.ts +1 -1
- package/types/fetch.d.ts +9 -8
- package/types/handlers.d.ts +4 -4
- package/types/index.d.ts +3 -1
- package/types/interceptors.d.ts +18 -1
- package/types/mock-agent.d.ts +4 -1
- package/types/mock-client.d.ts +1 -1
- package/types/mock-pool.d.ts +1 -1
- package/types/proxy-agent.d.ts +1 -1
- package/types/readable.d.ts +10 -7
- package/types/retry-handler.d.ts +3 -3
- package/types/webidl.d.ts +30 -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
|
@@ -50,6 +50,7 @@ function isClosed (readyState) {
|
|
|
50
50
|
* @param {EventTarget} target
|
|
51
51
|
* @param {(...args: ConstructorParameters<typeof Event>) => Event} eventFactory
|
|
52
52
|
* @param {EventInit | undefined} eventInitDict
|
|
53
|
+
* @returns {void}
|
|
53
54
|
*/
|
|
54
55
|
function fireEvent (e, target, eventFactory = (type, init) => new Event(type, init), eventInitDict = {}) {
|
|
55
56
|
// 1. If eventConstructor is not given, then let eventConstructor be Event.
|
|
@@ -72,11 +73,16 @@ function fireEvent (e, target, eventFactory = (type, init) => new Event(type, in
|
|
|
72
73
|
* @param {import('./websocket').Handler} handler
|
|
73
74
|
* @param {number} type Opcode
|
|
74
75
|
* @param {Buffer} data application data
|
|
76
|
+
* @returns {void}
|
|
75
77
|
*/
|
|
76
78
|
function websocketMessageReceived (handler, type, data) {
|
|
77
79
|
handler.onMessage(type, data)
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @param {Buffer} buffer
|
|
84
|
+
* @returns {ArrayBuffer}
|
|
85
|
+
*/
|
|
80
86
|
function toArrayBuffer (buffer) {
|
|
81
87
|
if (buffer.byteLength === buffer.buffer.byteLength) {
|
|
82
88
|
return buffer.buffer
|
|
@@ -89,6 +95,7 @@ function toArrayBuffer (buffer) {
|
|
|
89
95
|
* @see https://datatracker.ietf.org/doc/html/rfc2616
|
|
90
96
|
* @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407
|
|
91
97
|
* @param {string} protocol
|
|
98
|
+
* @returns {boolean}
|
|
92
99
|
*/
|
|
93
100
|
function isValidSubprotocol (protocol) {
|
|
94
101
|
// If present, this value indicates one
|
|
@@ -135,6 +142,7 @@ function isValidSubprotocol (protocol) {
|
|
|
135
142
|
/**
|
|
136
143
|
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4
|
|
137
144
|
* @param {number} code
|
|
145
|
+
* @returns {boolean}
|
|
138
146
|
*/
|
|
139
147
|
function isValidStatusCode (code) {
|
|
140
148
|
if (code >= 1000 && code < 1015) {
|
|
@@ -150,15 +158,34 @@ function isValidStatusCode (code) {
|
|
|
150
158
|
|
|
151
159
|
/**
|
|
152
160
|
* @param {import('./websocket').Handler} handler
|
|
161
|
+
* @param {number} code
|
|
153
162
|
* @param {string|undefined} reason
|
|
163
|
+
* @returns {void}
|
|
154
164
|
*/
|
|
155
|
-
function failWebsocketConnection (handler, reason) {
|
|
156
|
-
|
|
165
|
+
function failWebsocketConnection (handler, code, reason) {
|
|
166
|
+
// If _The WebSocket Connection is Established_ prior to the point where
|
|
167
|
+
// the endpoint is required to _Fail the WebSocket Connection_, the
|
|
168
|
+
// endpoint SHOULD send a Close frame with an appropriate status code
|
|
169
|
+
// (Section 7.4) before proceeding to _Close the WebSocket Connection_.
|
|
170
|
+
if (isEstablished(handler.readyState)) {
|
|
171
|
+
// avoid circular require - performance is not important here
|
|
172
|
+
const { closeWebSocketConnection } = require('./connection')
|
|
173
|
+
closeWebSocketConnection(handler, code, reason, false)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
handler.controller.abort()
|
|
177
|
+
|
|
178
|
+
if (handler.socket?.destroyed === false) {
|
|
179
|
+
handler.socket.destroy()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
handler.onFail(code, reason)
|
|
157
183
|
}
|
|
158
184
|
|
|
159
185
|
/**
|
|
160
186
|
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.5
|
|
161
187
|
* @param {number} opcode
|
|
188
|
+
* @returns {boolean}
|
|
162
189
|
*/
|
|
163
190
|
function isControlFrame (opcode) {
|
|
164
191
|
return (
|
|
@@ -168,14 +195,27 @@ function isControlFrame (opcode) {
|
|
|
168
195
|
)
|
|
169
196
|
}
|
|
170
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @param {number} opcode
|
|
200
|
+
* @returns {boolean}
|
|
201
|
+
*/
|
|
171
202
|
function isContinuationFrame (opcode) {
|
|
172
203
|
return opcode === opcodes.CONTINUATION
|
|
173
204
|
}
|
|
174
205
|
|
|
206
|
+
/**
|
|
207
|
+
* @param {number} opcode
|
|
208
|
+
* @returns {boolean}
|
|
209
|
+
*/
|
|
175
210
|
function isTextBinaryFrame (opcode) {
|
|
176
211
|
return opcode === opcodes.TEXT || opcode === opcodes.BINARY
|
|
177
212
|
}
|
|
178
213
|
|
|
214
|
+
/**
|
|
215
|
+
*
|
|
216
|
+
* @param {number} opcode
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
*/
|
|
179
219
|
function isValidOpcode (opcode) {
|
|
180
220
|
return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode)
|
|
181
221
|
}
|
|
@@ -209,6 +249,7 @@ function parseExtensions (extensions) {
|
|
|
209
249
|
* @see https://www.rfc-editor.org/rfc/rfc7692#section-7.1.2.2
|
|
210
250
|
* @description "client-max-window-bits = 1*DIGIT"
|
|
211
251
|
* @param {string} value
|
|
252
|
+
* @returns {boolean}
|
|
212
253
|
*/
|
|
213
254
|
function isValidClientWindowBits (value) {
|
|
214
255
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -222,22 +263,84 @@ function isValidClientWindowBits (value) {
|
|
|
222
263
|
return true
|
|
223
264
|
}
|
|
224
265
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
266
|
+
/**
|
|
267
|
+
* @see https://whatpr.org/websockets/48/7b748d3...d5570f3.html#get-a-url-record
|
|
268
|
+
* @param {string} url
|
|
269
|
+
* @param {string} [baseURL]
|
|
270
|
+
*/
|
|
271
|
+
function getURLRecord (url, baseURL) {
|
|
272
|
+
// 1. Let urlRecord be the result of applying the URL parser to url with baseURL .
|
|
273
|
+
// 2. If urlRecord is failure, then throw a " SyntaxError " DOMException .
|
|
274
|
+
let urlRecord
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
urlRecord = new URL(url, baseURL)
|
|
278
|
+
} catch (e) {
|
|
279
|
+
throw new DOMException(e, 'SyntaxError')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 3. If urlRecord ’s scheme is " http ", then set urlRecord ’s scheme to " ws ".
|
|
283
|
+
// 4. Otherwise, if urlRecord ’s scheme is " https ", set urlRecord ’s scheme to " wss ".
|
|
284
|
+
if (urlRecord.protocol === 'http:') {
|
|
285
|
+
urlRecord.protocol = 'ws:'
|
|
286
|
+
} else if (urlRecord.protocol === 'https:') {
|
|
287
|
+
urlRecord.protocol = 'wss:'
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 5. If urlRecord ’s scheme is not " ws " or " wss ", then throw a " SyntaxError " DOMException .
|
|
291
|
+
if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
|
|
292
|
+
throw new DOMException('expected a ws: or wss: url', 'SyntaxError')
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// If urlRecord ’s fragment is non-null, then throw a " SyntaxError " DOMException .
|
|
296
|
+
if (urlRecord.hash.length || urlRecord.href.endsWith('#')) {
|
|
297
|
+
throw new DOMException('hash', 'SyntaxError')
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Return urlRecord .
|
|
301
|
+
return urlRecord
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// https://whatpr.org/websockets/48.html#validate-close-code-and-reason
|
|
305
|
+
function validateCloseCodeAndReason (code, reason) {
|
|
306
|
+
// 1. If code is not null, but is neither an integer equal to
|
|
307
|
+
// 1000 nor an integer in the range 3000 to 4999, inclusive,
|
|
308
|
+
// throw an "InvalidAccessError" DOMException.
|
|
309
|
+
if (code !== null) {
|
|
310
|
+
if (code !== 1000 && (code < 3000 || code > 4999)) {
|
|
311
|
+
throw new DOMException('invalid code', 'InvalidAccessError')
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 2. If reason is not null, then:
|
|
316
|
+
if (reason !== null) {
|
|
317
|
+
// 2.1. Let reasonBytes be the result of UTF-8 encoding reason.
|
|
318
|
+
// 2.2. If reasonBytes is longer than 123 bytes, then throw a
|
|
319
|
+
// "SyntaxError" DOMException.
|
|
320
|
+
const reasonBytesLength = Buffer.byteLength(reason)
|
|
321
|
+
|
|
322
|
+
if (reasonBytesLength > 123) {
|
|
323
|
+
throw new DOMException(`Reason must be less than 123 bytes; received ${reasonBytesLength}`, 'SyntaxError')
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
228
327
|
|
|
229
328
|
/**
|
|
230
329
|
* Converts a Buffer to utf-8, even on platforms without icu.
|
|
231
|
-
* @
|
|
330
|
+
* @type {(buffer: Buffer) => string}
|
|
232
331
|
*/
|
|
233
|
-
const utf8Decode =
|
|
234
|
-
|
|
235
|
-
|
|
332
|
+
const utf8Decode = (() => {
|
|
333
|
+
if (typeof process.versions.icu === 'string') {
|
|
334
|
+
const fatalDecoder = new TextDecoder('utf-8', { fatal: true })
|
|
335
|
+
return fatalDecoder.decode.bind(fatalDecoder)
|
|
336
|
+
}
|
|
337
|
+
return function (buffer) {
|
|
236
338
|
if (isUtf8(buffer)) {
|
|
237
339
|
return buffer.toString('utf-8')
|
|
238
340
|
}
|
|
239
341
|
throw new TypeError('Invalid utf-8 received.')
|
|
240
342
|
}
|
|
343
|
+
})()
|
|
241
344
|
|
|
242
345
|
module.exports = {
|
|
243
346
|
isConnecting,
|
|
@@ -256,5 +359,7 @@ module.exports = {
|
|
|
256
359
|
isValidOpcode,
|
|
257
360
|
parseExtensions,
|
|
258
361
|
isValidClientWindowBits,
|
|
259
|
-
toArrayBuffer
|
|
362
|
+
toArrayBuffer,
|
|
363
|
+
getURLRecord,
|
|
364
|
+
validateCloseCodeAndReason
|
|
260
365
|
}
|
|
@@ -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, sendHints, opcodes
|
|
6
|
+
const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants')
|
|
7
7
|
const {
|
|
8
8
|
isConnecting,
|
|
9
9
|
isEstablished,
|
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
failWebsocketConnection,
|
|
14
14
|
utf8Decode,
|
|
15
15
|
toArrayBuffer,
|
|
16
|
-
|
|
16
|
+
getURLRecord
|
|
17
17
|
} = require('./util')
|
|
18
18
|
const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
|
|
19
19
|
const { ByteParser } = require('./receiver')
|
|
@@ -22,15 +22,13 @@ const { getGlobalDispatcher } = require('../../global')
|
|
|
22
22
|
const { types } = require('node:util')
|
|
23
23
|
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
|
|
24
24
|
const { SendQueue } = require('./sender')
|
|
25
|
-
const { WebsocketFrameSend } = require('./frame')
|
|
26
25
|
const { channels } = require('../../core/diagnostics')
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
* @typedef {object} Handler
|
|
30
29
|
* @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
|
|
31
|
-
* @property {(reason: any) => void} onFail
|
|
30
|
+
* @property {(code: number, reason: any) => void} onFail
|
|
32
31
|
* @property {(opcode: number, data: Buffer) => void} onMessage
|
|
33
|
-
* @property {(code: number, reason: any, reasonByteLength: number) => void} onClose
|
|
34
32
|
* @property {(error: Error) => void} onParserError
|
|
35
33
|
* @property {() => void} onParserDrain
|
|
36
34
|
* @property {(chunk: Buffer) => void} onSocketData
|
|
@@ -39,8 +37,9 @@ const { channels } = require('../../core/diagnostics')
|
|
|
39
37
|
*
|
|
40
38
|
* @property {number} readyState
|
|
41
39
|
* @property {import('stream').Duplex} socket
|
|
42
|
-
* @property {number} closeState
|
|
43
|
-
* @property {
|
|
40
|
+
* @property {Set<number>} closeState
|
|
41
|
+
* @property {import('../fetch/index').Fetch} controller
|
|
42
|
+
* @property {boolean} [wasEverConnected=false]
|
|
44
43
|
*/
|
|
45
44
|
|
|
46
45
|
// https://websockets.spec.whatwg.org/#interface-definition
|
|
@@ -62,10 +61,9 @@ class WebSocket extends EventTarget {
|
|
|
62
61
|
/** @type {Handler} */
|
|
63
62
|
#handler = {
|
|
64
63
|
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
|
65
|
-
onFail: (reason) => this.#onFail(reason),
|
|
64
|
+
onFail: (code, reason) => this.#onFail(code, reason),
|
|
66
65
|
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
|
67
|
-
|
|
68
|
-
onParserError: (err) => this.#onParserError(err),
|
|
66
|
+
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
|
|
69
67
|
onParserDrain: () => this.#onParserDrain(),
|
|
70
68
|
onSocketData: (chunk) => {
|
|
71
69
|
if (!this.#parser.write(chunk)) {
|
|
@@ -85,12 +83,12 @@ class WebSocket extends EventTarget {
|
|
|
85
83
|
|
|
86
84
|
readyState: states.CONNECTING,
|
|
87
85
|
socket: null,
|
|
88
|
-
closeState:
|
|
89
|
-
|
|
86
|
+
closeState: new Set(),
|
|
87
|
+
controller: null,
|
|
88
|
+
wasEverConnected: false
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
#url
|
|
93
|
-
#controller
|
|
94
92
|
#binaryType
|
|
95
93
|
/** @type {import('./receiver').ByteParser} */
|
|
96
94
|
#parser
|
|
@@ -102,6 +100,8 @@ class WebSocket extends EventTarget {
|
|
|
102
100
|
constructor (url, protocols = []) {
|
|
103
101
|
super()
|
|
104
102
|
|
|
103
|
+
webidl.util.markAsUncloneable(this)
|
|
104
|
+
|
|
105
105
|
const prefix = 'WebSocket constructor'
|
|
106
106
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
107
107
|
|
|
@@ -113,45 +113,16 @@ class WebSocket extends EventTarget {
|
|
|
113
113
|
// 1. Let baseURL be this's relevant settings object's API base URL.
|
|
114
114
|
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
|
115
115
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
urlRecord = new URL(url, baseURL)
|
|
121
|
-
} catch (e) {
|
|
122
|
-
// 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
|
|
123
|
-
throw new DOMException(e, 'SyntaxError')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws".
|
|
127
|
-
if (urlRecord.protocol === 'http:') {
|
|
128
|
-
urlRecord.protocol = 'ws:'
|
|
129
|
-
} else if (urlRecord.protocol === 'https:') {
|
|
130
|
-
// 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss".
|
|
131
|
-
urlRecord.protocol = 'wss:'
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
|
|
135
|
-
if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
|
|
136
|
-
throw new DOMException(
|
|
137
|
-
`Expected a ws: or wss: protocol, got ${urlRecord.protocol}`,
|
|
138
|
-
'SyntaxError'
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError"
|
|
143
|
-
// DOMException.
|
|
144
|
-
if (urlRecord.hash || urlRecord.href.endsWith('#')) {
|
|
145
|
-
throw new DOMException('Got fragment', 'SyntaxError')
|
|
146
|
-
}
|
|
116
|
+
// 2. Let urlRecord be the result of getting a URL record given url and baseURL.
|
|
117
|
+
const urlRecord = getURLRecord(url, baseURL)
|
|
147
118
|
|
|
148
|
-
//
|
|
119
|
+
// 3. If protocols is a string, set protocols to a sequence consisting
|
|
149
120
|
// of just that string.
|
|
150
121
|
if (typeof protocols === 'string') {
|
|
151
122
|
protocols = [protocols]
|
|
152
123
|
}
|
|
153
124
|
|
|
154
|
-
//
|
|
125
|
+
// 4. If any of the values in protocols occur more than once or otherwise
|
|
155
126
|
// fail to match the requirements for elements that comprise the value
|
|
156
127
|
// of `Sec-WebSocket-Protocol` fields as defined by The WebSocket
|
|
157
128
|
// protocol, then throw a "SyntaxError" DOMException.
|
|
@@ -163,17 +134,16 @@ class WebSocket extends EventTarget {
|
|
|
163
134
|
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
|
164
135
|
}
|
|
165
136
|
|
|
166
|
-
//
|
|
137
|
+
// 5. Set this's url to urlRecord.
|
|
167
138
|
this.#url = new URL(urlRecord.href)
|
|
168
139
|
|
|
169
|
-
//
|
|
140
|
+
// 6. Let client be this's relevant settings object.
|
|
170
141
|
const client = environmentSettingsObject.settingsObject
|
|
171
142
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
this.#controller = establishWebSocketConnection(
|
|
143
|
+
// 7. Run this step in parallel:
|
|
144
|
+
// 7.1. Establish a WebSocket connection given urlRecord, protocols,
|
|
145
|
+
// and client.
|
|
146
|
+
this.#handler.controller = establishWebSocketConnection(
|
|
177
147
|
urlRecord,
|
|
178
148
|
protocols,
|
|
179
149
|
client,
|
|
@@ -186,8 +156,6 @@ class WebSocket extends EventTarget {
|
|
|
186
156
|
// be CONNECTING (0).
|
|
187
157
|
this.#handler.readyState = WebSocket.CONNECTING
|
|
188
158
|
|
|
189
|
-
this.#handler.closeState = sentCloseFrameState.NOT_SENT
|
|
190
|
-
|
|
191
159
|
// The extensions attribute must initially return the empty string.
|
|
192
160
|
|
|
193
161
|
// The protocol attribute must initially return the empty string.
|
|
@@ -215,34 +183,14 @@ class WebSocket extends EventTarget {
|
|
|
215
183
|
reason = webidl.converters.USVString(reason)
|
|
216
184
|
}
|
|
217
185
|
|
|
218
|
-
// 1. If code is
|
|
219
|
-
|
|
220
|
-
// "InvalidAccessError" DOMException.
|
|
221
|
-
if (code !== undefined) {
|
|
222
|
-
if (code !== 1000 && (code < 3000 || code > 4999)) {
|
|
223
|
-
throw new DOMException('invalid code', 'InvalidAccessError')
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
let reasonByteLength = 0
|
|
186
|
+
// 1. If code is the special value "missing", then set code to null.
|
|
187
|
+
code ??= null
|
|
228
188
|
|
|
229
|
-
// 2. If reason is
|
|
230
|
-
|
|
231
|
-
// 1. Let reasonBytes be the result of encoding reason.
|
|
232
|
-
// 2. If reasonBytes is longer than 123 bytes, then throw a
|
|
233
|
-
// "SyntaxError" DOMException.
|
|
234
|
-
reasonByteLength = Buffer.byteLength(reason)
|
|
235
|
-
|
|
236
|
-
if (reasonByteLength > 123) {
|
|
237
|
-
throw new DOMException(
|
|
238
|
-
`Reason must be less than 123 bytes; received ${reasonByteLength}`,
|
|
239
|
-
'SyntaxError'
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
189
|
+
// 2. If reason is the special value "missing", then set reason to the empty string.
|
|
190
|
+
reason ??= ''
|
|
243
191
|
|
|
244
|
-
// 3.
|
|
245
|
-
closeWebSocketConnection(this.#handler, code, reason,
|
|
192
|
+
// 3. Close the WebSocket with this, code, and reason.
|
|
193
|
+
closeWebSocketConnection(this.#handler, code, reason, true)
|
|
246
194
|
}
|
|
247
195
|
|
|
248
196
|
/**
|
|
@@ -324,7 +272,7 @@ class WebSocket extends EventTarget {
|
|
|
324
272
|
this.#sendQueue.add(data, () => {
|
|
325
273
|
this.#bufferedAmount -= data.byteLength
|
|
326
274
|
}, sendHints.typedArray)
|
|
327
|
-
} else if (data
|
|
275
|
+
} else if (webidl.is.Blob(data)) {
|
|
328
276
|
// If the WebSocket connection is established, and the WebSocket
|
|
329
277
|
// closing handshake has not yet started, then the user agent must
|
|
330
278
|
// send a WebSocket Message comprised of data using a binary frame
|
|
@@ -515,15 +463,7 @@ class WebSocket extends EventTarget {
|
|
|
515
463
|
fireEvent('open', this)
|
|
516
464
|
}
|
|
517
465
|
|
|
518
|
-
#onFail (reason) {
|
|
519
|
-
this.#controller.abort()
|
|
520
|
-
|
|
521
|
-
if (this.#handler.socket && !this.#handler.socket.destroyed) {
|
|
522
|
-
this.#handler.socket.destroy()
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
this.#handler.readyState = states.CLOSED
|
|
526
|
-
|
|
466
|
+
#onFail (code, reason) {
|
|
527
467
|
if (reason) {
|
|
528
468
|
// TODO: process.nextTick
|
|
529
469
|
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
|
@@ -531,6 +471,16 @@ class WebSocket extends EventTarget {
|
|
|
531
471
|
message: reason
|
|
532
472
|
})
|
|
533
473
|
}
|
|
474
|
+
|
|
475
|
+
if (!this.#handler.wasEverConnected) {
|
|
476
|
+
this.#handler.readyState = states.CLOSED
|
|
477
|
+
|
|
478
|
+
// If the WebSocket connection could not be established, it is also said
|
|
479
|
+
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
|
480
|
+
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
|
481
|
+
wasClean: false, code, reason
|
|
482
|
+
})
|
|
483
|
+
}
|
|
534
484
|
}
|
|
535
485
|
|
|
536
486
|
#onMessage (type, data) {
|
|
@@ -548,7 +498,7 @@ class WebSocket extends EventTarget {
|
|
|
548
498
|
try {
|
|
549
499
|
dataForEvent = utf8Decode(data)
|
|
550
500
|
} catch {
|
|
551
|
-
failWebsocketConnection(this.#handler, 'Received invalid UTF-8 in text frame.')
|
|
501
|
+
failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')
|
|
552
502
|
return
|
|
553
503
|
}
|
|
554
504
|
} else if (type === opcodes.BINARY) {
|
|
@@ -574,81 +524,6 @@ class WebSocket extends EventTarget {
|
|
|
574
524
|
})
|
|
575
525
|
}
|
|
576
526
|
|
|
577
|
-
#onClose (code, reason, reasonByteLength) {
|
|
578
|
-
if (isClosing(this.#handler.readyState) || isClosed(this.#handler.readyState)) {
|
|
579
|
-
// If this's ready state is CLOSING (2) or CLOSED (3)
|
|
580
|
-
// Do nothing.
|
|
581
|
-
} else if (!isEstablished(this.#handler.readyState)) {
|
|
582
|
-
// If the WebSocket connection is not yet established
|
|
583
|
-
// Fail the WebSocket connection and set this's ready state
|
|
584
|
-
// to CLOSING (2).
|
|
585
|
-
failWebsocketConnection(this.#handler, 'Connection was closed before it was established.')
|
|
586
|
-
this.#handler.readyState = states.CLOSING
|
|
587
|
-
} else if (this.#handler.closeState === sentCloseFrameState.NOT_SENT) {
|
|
588
|
-
// If the WebSocket closing handshake has not yet been started
|
|
589
|
-
// Start the WebSocket closing handshake and set this's ready
|
|
590
|
-
// state to CLOSING (2).
|
|
591
|
-
// - If neither code nor reason is present, the WebSocket Close
|
|
592
|
-
// message must not have a body.
|
|
593
|
-
// - If code is present, then the status code to use in the
|
|
594
|
-
// WebSocket Close message must be the integer given by code.
|
|
595
|
-
// - If reason is also present, then reasonBytes must be
|
|
596
|
-
// provided in the Close message after the status code.
|
|
597
|
-
|
|
598
|
-
this.#handler.closeState = sentCloseFrameState.PROCESSING
|
|
599
|
-
|
|
600
|
-
const frame = new WebsocketFrameSend()
|
|
601
|
-
|
|
602
|
-
// If neither code nor reason is present, the WebSocket Close
|
|
603
|
-
// message must not have a body.
|
|
604
|
-
|
|
605
|
-
// If code is present, then the status code to use in the
|
|
606
|
-
// WebSocket Close message must be the integer given by code.
|
|
607
|
-
if (code !== undefined && reason === undefined) {
|
|
608
|
-
frame.frameData = Buffer.allocUnsafe(2)
|
|
609
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
610
|
-
} else if (code !== undefined && reason !== undefined) {
|
|
611
|
-
// If reason is also present, then reasonBytes must be
|
|
612
|
-
// provided in the Close message after the status code.
|
|
613
|
-
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
|
|
614
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
615
|
-
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
616
|
-
frame.frameData.write(reason, 2, 'utf-8')
|
|
617
|
-
} else {
|
|
618
|
-
frame.frameData = emptyBuffer
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
this.#handler.socket.write(frame.createFrame(opcodes.CLOSE))
|
|
622
|
-
|
|
623
|
-
this.#handler.closeState = sentCloseFrameState.SENT
|
|
624
|
-
|
|
625
|
-
// Upon either sending or receiving a Close control frame, it is said
|
|
626
|
-
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
627
|
-
// WebSocket connection is in the CLOSING state.
|
|
628
|
-
this.#handler.readyState = states.CLOSING
|
|
629
|
-
} else {
|
|
630
|
-
// Otherwise
|
|
631
|
-
// Set this's ready state to CLOSING (2).
|
|
632
|
-
this.#handler.readyState = states.CLOSING
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
#onParserError (err) {
|
|
637
|
-
let message
|
|
638
|
-
let code
|
|
639
|
-
|
|
640
|
-
if (err instanceof CloseEvent) {
|
|
641
|
-
message = err.reason
|
|
642
|
-
code = err.code
|
|
643
|
-
} else {
|
|
644
|
-
message = err.message
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message }))
|
|
648
|
-
|
|
649
|
-
closeWebSocketConnection(this.#handler, code)
|
|
650
|
-
}
|
|
651
|
-
|
|
652
527
|
#onParserDrain () {
|
|
653
528
|
this.#handler.socket.resume()
|
|
654
529
|
}
|
|
@@ -661,7 +536,9 @@ class WebSocket extends EventTarget {
|
|
|
661
536
|
// If the TCP connection was closed after the
|
|
662
537
|
// WebSocket closing handshake was completed, the WebSocket connection
|
|
663
538
|
// is said to have been closed _cleanly_.
|
|
664
|
-
const wasClean =
|
|
539
|
+
const wasClean =
|
|
540
|
+
this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
|
541
|
+
this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
|
665
542
|
|
|
666
543
|
let code = 1005
|
|
667
544
|
let reason = ''
|
|
@@ -671,7 +548,7 @@ class WebSocket extends EventTarget {
|
|
|
671
548
|
if (result && !result.error) {
|
|
672
549
|
code = result.code ?? 1005
|
|
673
550
|
reason = result.reason
|
|
674
|
-
} else if (!this.#handler.
|
|
551
|
+
} else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
|
675
552
|
// If _The WebSocket
|
|
676
553
|
// Connection is Closed_ and no Close control frame was received by the
|
|
677
554
|
// endpoint (such as could occur if the underlying transport connection
|
|
@@ -793,7 +670,7 @@ webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = functio
|
|
|
793
670
|
|
|
794
671
|
webidl.converters.WebSocketSendData = function (V) {
|
|
795
672
|
if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
|
|
796
|
-
if (V
|
|
673
|
+
if (webidl.is.Blob(V)) {
|
|
797
674
|
return V
|
|
798
675
|
}
|
|
799
676
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "7.0.0-alpha.
|
|
3
|
+
"version": "7.0.0-alpha.10",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -62,22 +62,26 @@
|
|
|
62
62
|
"main": "index.js",
|
|
63
63
|
"types": "index.d.ts",
|
|
64
64
|
"scripts": {
|
|
65
|
-
"build:node": "
|
|
66
|
-
"prebuild:wasm": "node build/wasm.js --prebuild",
|
|
65
|
+
"build:node": "esbuild index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js",
|
|
67
66
|
"build:wasm": "node build/wasm.js --docker",
|
|
68
67
|
"generate-pem": "node scripts/generate-pem.js",
|
|
69
68
|
"lint": "eslint --cache",
|
|
70
69
|
"lint:fix": "eslint --fix --cache",
|
|
71
70
|
"test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
|
|
72
71
|
"test:javascript": "npm run test:javascript:no-jest && npm run test:jest",
|
|
73
|
-
"test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test",
|
|
72
|
+
"test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test",
|
|
74
73
|
"test:javascript:without-intl": "npm run test:javascript:no-jest",
|
|
75
74
|
"test:busboy": "borp -p \"test/busboy/*.js\"",
|
|
76
75
|
"test:cache": "borp -p \"test/cache/*.js\"",
|
|
76
|
+
"test:sqlite": "NODE_OPTIONS=--experimental-sqlite borp -p \"test/cache-interceptor/*.js\"",
|
|
77
|
+
"test:cache-interceptor": "borp -p \"test/cache-interceptor/*.js\"",
|
|
77
78
|
"test:cookies": "borp -p \"test/cookie/*.js\"",
|
|
78
79
|
"test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"",
|
|
79
80
|
"test:fuzzing": "node test/fuzzing/fuzzing.test.js",
|
|
80
81
|
"test:fetch": "npm run build:node && borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
|
|
82
|
+
"test:h2": "npm run test:h2:core && npm run test:h2:fetch",
|
|
83
|
+
"test:h2:core": "borp -p \"test/http2*.js\"",
|
|
84
|
+
"test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"",
|
|
81
85
|
"test:interceptors": "borp -p \"test/interceptors/*.js\"",
|
|
82
86
|
"test:jest": "cross-env NODE_V8_COVERAGE= jest",
|
|
83
87
|
"test:unit": "borp --expose-gc -p \"test/*.js\"",
|
|
@@ -104,13 +108,14 @@
|
|
|
104
108
|
"devDependencies": {
|
|
105
109
|
"@fastify/busboy": "3.0.0",
|
|
106
110
|
"@matteo.collina/tspl": "^0.1.1",
|
|
107
|
-
"@sinonjs/fake-timers": "^
|
|
108
|
-
"@types/node": "
|
|
111
|
+
"@sinonjs/fake-timers": "^12.0.0",
|
|
112
|
+
"@types/node": "^18.19.50",
|
|
109
113
|
"abort-controller": "^3.0.0",
|
|
110
|
-
"borp": "^0.
|
|
114
|
+
"borp": "^0.19.0",
|
|
111
115
|
"c8": "^10.0.0",
|
|
112
116
|
"cross-env": "^7.0.3",
|
|
113
117
|
"dns-packet": "^5.4.0",
|
|
118
|
+
"esbuild": "^0.24.0",
|
|
114
119
|
"eslint": "^9.9.0",
|
|
115
120
|
"fast-check": "^3.17.1",
|
|
116
121
|
"https-pem": "^3.0.0",
|
|
@@ -118,14 +123,13 @@
|
|
|
118
123
|
"jest": "^29.0.2",
|
|
119
124
|
"neostandard": "^0.11.2",
|
|
120
125
|
"node-forge": "^1.3.1",
|
|
121
|
-
"pre-commit": "^1.2.2",
|
|
122
126
|
"proxy": "^2.1.1",
|
|
123
|
-
"tsd": "^0.31.
|
|
124
|
-
"typescript": "^5.
|
|
127
|
+
"tsd": "^0.31.2",
|
|
128
|
+
"typescript": "^5.6.2",
|
|
125
129
|
"ws": "^8.11.0"
|
|
126
130
|
},
|
|
127
131
|
"engines": {
|
|
128
|
-
"node": ">=18.
|
|
132
|
+
"node": ">=20.18.1"
|
|
129
133
|
},
|
|
130
134
|
"tsd": {
|
|
131
135
|
"directory": "test/types",
|
package/types/agent.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ declare class Agent extends Dispatcher {
|
|
|
11
11
|
/** `true` after `dispatcher.destroyed()` has been called or `dispatcher.close()` has been called and the dispatcher shutdown has completed. */
|
|
12
12
|
destroyed: boolean
|
|
13
13
|
/** Dispatches a request. */
|
|
14
|
-
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.
|
|
14
|
+
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
declare namespace Agent {
|