undici 6.19.8 → 7.0.0-alpha.1
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 +5 -9
- package/docs/docs/api/Agent.md +0 -3
- package/docs/docs/api/Client.md +0 -2
- package/docs/docs/api/Dispatcher.md +204 -6
- package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
- package/docs/docs/api/Fetch.md +1 -0
- package/docs/docs/api/Pool.md +0 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/index.js +0 -4
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +3 -4
- package/lib/api/api-request.js +29 -46
- package/lib/api/api-stream.js +36 -49
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +71 -27
- package/lib/core/connect.js +39 -24
- package/lib/core/errors.js +17 -4
- package/lib/core/request.js +7 -5
- package/lib/core/symbols.js +0 -1
- package/lib/core/tree.js +6 -0
- package/lib/core/util.js +1 -11
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +44 -39
- package/lib/dispatcher/client.js +3 -27
- package/lib/dispatcher/dispatcher-base.js +2 -34
- package/lib/dispatcher/dispatcher.js +3 -24
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +3 -6
- package/lib/handler/decorator-handler.js +24 -0
- package/lib/handler/redirect-handler.js +9 -0
- package/lib/handler/retry-handler.js +22 -3
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +89 -0
- 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-client.js +2 -2
- package/lib/mock/mock-pool.js +2 -2
- package/lib/mock/mock-symbols.js +1 -0
- package/lib/util/timers.js +324 -44
- package/lib/web/cookies/index.js +15 -13
- package/lib/web/cookies/parse.js +2 -2
- package/lib/web/eventsource/eventsource-stream.js +9 -8
- package/lib/web/eventsource/eventsource.js +10 -6
- package/lib/web/fetch/body.js +4 -6
- package/lib/web/fetch/data-url.js +1 -1
- package/lib/web/fetch/formdata-parser.js +1 -2
- package/lib/web/fetch/formdata.js +28 -37
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +7 -8
- package/lib/web/fetch/request.js +7 -24
- package/lib/web/fetch/response.js +9 -22
- package/lib/web/fetch/symbols.js +0 -1
- package/lib/web/fetch/util.js +3 -12
- package/lib/web/fetch/webidl.js +73 -62
- package/lib/web/websocket/connection.js +26 -174
- package/lib/web/websocket/constants.js +1 -1
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +28 -26
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/util.js +20 -74
- package/lib/web/websocket/websocket.js +294 -70
- package/package.json +16 -29
- 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/client.d.ts +11 -12
- package/types/diagnostics-channel.d.ts +10 -10
- package/types/dispatcher.d.ts +96 -97
- package/types/env-http-proxy-agent.d.ts +2 -2
- package/types/errors.d.ts +53 -47
- package/types/eventsource.d.ts +0 -2
- package/types/fetch.d.ts +8 -8
- 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 +4 -4
- package/types/header.d.ts +157 -1
- package/types/index.d.ts +42 -46
- package/types/interceptors.d.ts +10 -8
- package/types/mock-agent.d.ts +18 -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 -42
- 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 +14 -9
- package/types/retry-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +8 -8
- package/types/util.d.ts +3 -3
- package/types/utility.d.ts +7 -0
- package/types/webidl.d.ts +22 -4
- package/types/websocket.d.ts +1 -3
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/web/fetch/file.js +0 -126
- 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
|
@@ -3,30 +3,45 @@
|
|
|
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 } = require('./constants')
|
|
7
|
-
const {
|
|
8
|
-
kWebSocketURL,
|
|
9
|
-
kReadyState,
|
|
10
|
-
kController,
|
|
11
|
-
kBinaryType,
|
|
12
|
-
kResponse,
|
|
13
|
-
kSentClose,
|
|
14
|
-
kByteParser
|
|
15
|
-
} = require('./symbols')
|
|
6
|
+
const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes, emptyBuffer } = require('./constants')
|
|
16
7
|
const {
|
|
17
8
|
isConnecting,
|
|
18
9
|
isEstablished,
|
|
19
10
|
isClosing,
|
|
20
11
|
isValidSubprotocol,
|
|
21
|
-
fireEvent
|
|
12
|
+
fireEvent,
|
|
13
|
+
failWebsocketConnection,
|
|
14
|
+
utf8Decode,
|
|
15
|
+
toArrayBuffer,
|
|
16
|
+
isClosed
|
|
22
17
|
} = require('./util')
|
|
23
18
|
const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
|
|
24
19
|
const { ByteParser } = require('./receiver')
|
|
25
|
-
const { kEnumerableProperty
|
|
20
|
+
const { kEnumerableProperty } = require('../../core/util')
|
|
26
21
|
const { getGlobalDispatcher } = require('../../global')
|
|
27
22
|
const { types } = require('node:util')
|
|
28
|
-
const { ErrorEvent, CloseEvent } = require('./events')
|
|
23
|
+
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
|
|
29
24
|
const { SendQueue } = require('./sender')
|
|
25
|
+
const { WebsocketFrameSend } = require('./frame')
|
|
26
|
+
const { channels } = require('../../core/diagnostics')
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} Handler
|
|
30
|
+
* @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
|
|
31
|
+
* @property {(reason: any) => void} onFail
|
|
32
|
+
* @property {(opcode: number, data: Buffer) => void} onMessage
|
|
33
|
+
* @property {(code: number, reason: any, reasonByteLength: number) => void} onClose
|
|
34
|
+
* @property {(error: Error) => void} onParserError
|
|
35
|
+
* @property {() => void} onParserDrain
|
|
36
|
+
* @property {(chunk: Buffer) => void} onSocketData
|
|
37
|
+
* @property {(err: Error) => void} onSocketError
|
|
38
|
+
* @property {() => void} onSocketClose
|
|
39
|
+
*
|
|
40
|
+
* @property {number} readyState
|
|
41
|
+
* @property {import('stream').Duplex} socket
|
|
42
|
+
* @property {number} closeState
|
|
43
|
+
* @property {boolean} receivedClose
|
|
44
|
+
*/
|
|
30
45
|
|
|
31
46
|
// https://websockets.spec.whatwg.org/#interface-definition
|
|
32
47
|
class WebSocket extends EventTarget {
|
|
@@ -44,6 +59,42 @@ class WebSocket extends EventTarget {
|
|
|
44
59
|
/** @type {SendQueue} */
|
|
45
60
|
#sendQueue
|
|
46
61
|
|
|
62
|
+
/** @type {Handler} */
|
|
63
|
+
#handler = {
|
|
64
|
+
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
|
65
|
+
onFail: (reason) => this.#onFail(reason),
|
|
66
|
+
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
|
67
|
+
onClose: (code, reason, reasonByteLength) => this.#onClose(code, reason, reasonByteLength),
|
|
68
|
+
onParserError: (err) => this.#onParserError(err),
|
|
69
|
+
onParserDrain: () => this.#onParserDrain(),
|
|
70
|
+
onSocketData: (chunk) => {
|
|
71
|
+
if (!this.#parser.write(chunk)) {
|
|
72
|
+
this.#handler.socket.pause()
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
onSocketError: (err) => {
|
|
76
|
+
this.#handler.readyState = states.CLOSING
|
|
77
|
+
|
|
78
|
+
if (channels.socketError.hasSubscribers) {
|
|
79
|
+
channels.socketError.publish(err)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.#handler.socket.destroy()
|
|
83
|
+
},
|
|
84
|
+
onSocketClose: () => this.#onSocketClose(),
|
|
85
|
+
|
|
86
|
+
readyState: states.CONNECTING,
|
|
87
|
+
socket: null,
|
|
88
|
+
closeState: sentCloseFrameState.NOT_SENT,
|
|
89
|
+
receivedClose: false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#url
|
|
93
|
+
#controller
|
|
94
|
+
#binaryType
|
|
95
|
+
/** @type {import('./receiver').ByteParser} */
|
|
96
|
+
#parser
|
|
97
|
+
|
|
47
98
|
/**
|
|
48
99
|
* @param {string} url
|
|
49
100
|
* @param {string|string[]} protocols
|
|
@@ -56,7 +107,7 @@ class WebSocket extends EventTarget {
|
|
|
56
107
|
|
|
57
108
|
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
|
|
58
109
|
|
|
59
|
-
url = webidl.converters.USVString(url
|
|
110
|
+
url = webidl.converters.USVString(url)
|
|
60
111
|
protocols = options.protocols
|
|
61
112
|
|
|
62
113
|
// 1. Let baseURL be this's relevant settings object's API base URL.
|
|
@@ -113,7 +164,7 @@ class WebSocket extends EventTarget {
|
|
|
113
164
|
}
|
|
114
165
|
|
|
115
166
|
// 10. Set this's url to urlRecord.
|
|
116
|
-
this
|
|
167
|
+
this.#url = new URL(urlRecord.href)
|
|
117
168
|
|
|
118
169
|
// 11. Let client be this's relevant settings object.
|
|
119
170
|
const client = environmentSettingsObject.settingsObject
|
|
@@ -122,21 +173,20 @@ class WebSocket extends EventTarget {
|
|
|
122
173
|
|
|
123
174
|
// 1. Establish a WebSocket connection given urlRecord, protocols,
|
|
124
175
|
// and client.
|
|
125
|
-
this
|
|
176
|
+
this.#controller = establishWebSocketConnection(
|
|
126
177
|
urlRecord,
|
|
127
178
|
protocols,
|
|
128
179
|
client,
|
|
129
|
-
this,
|
|
130
|
-
(response, extensions) => this.#onConnectionEstablished(response, extensions),
|
|
180
|
+
this.#handler,
|
|
131
181
|
options
|
|
132
182
|
)
|
|
133
183
|
|
|
134
184
|
// Each WebSocket object has an associated ready state, which is a
|
|
135
185
|
// number representing the state of the connection. Initially it must
|
|
136
186
|
// be CONNECTING (0).
|
|
137
|
-
this
|
|
187
|
+
this.#handler.readyState = WebSocket.CONNECTING
|
|
138
188
|
|
|
139
|
-
this
|
|
189
|
+
this.#handler.closeState = sentCloseFrameState.NOT_SENT
|
|
140
190
|
|
|
141
191
|
// The extensions attribute must initially return the empty string.
|
|
142
192
|
|
|
@@ -144,7 +194,7 @@ class WebSocket extends EventTarget {
|
|
|
144
194
|
|
|
145
195
|
// Each WebSocket object has an associated binary type, which is a
|
|
146
196
|
// BinaryType. Initially it must be "blob".
|
|
147
|
-
this
|
|
197
|
+
this.#binaryType = 'blob'
|
|
148
198
|
}
|
|
149
199
|
|
|
150
200
|
/**
|
|
@@ -162,7 +212,7 @@ class WebSocket extends EventTarget {
|
|
|
162
212
|
}
|
|
163
213
|
|
|
164
214
|
if (reason !== undefined) {
|
|
165
|
-
reason = webidl.converters.USVString(reason
|
|
215
|
+
reason = webidl.converters.USVString(reason)
|
|
166
216
|
}
|
|
167
217
|
|
|
168
218
|
// 1. If code is present, but is neither an integer equal to 1000 nor an
|
|
@@ -192,7 +242,7 @@ class WebSocket extends EventTarget {
|
|
|
192
242
|
}
|
|
193
243
|
|
|
194
244
|
// 3. Run the first matching steps from the following list:
|
|
195
|
-
closeWebSocketConnection(this, code, reason, reasonByteLength)
|
|
245
|
+
closeWebSocketConnection(this.#handler, code, reason, reasonByteLength)
|
|
196
246
|
}
|
|
197
247
|
|
|
198
248
|
/**
|
|
@@ -209,7 +259,7 @@ class WebSocket extends EventTarget {
|
|
|
209
259
|
|
|
210
260
|
// 1. If this's ready state is CONNECTING, then throw an
|
|
211
261
|
// "InvalidStateError" DOMException.
|
|
212
|
-
if (isConnecting(this)) {
|
|
262
|
+
if (isConnecting(this.#handler.readyState)) {
|
|
213
263
|
throw new DOMException('Sent before connected.', 'InvalidStateError')
|
|
214
264
|
}
|
|
215
265
|
|
|
@@ -217,7 +267,7 @@ class WebSocket extends EventTarget {
|
|
|
217
267
|
// https://datatracker.ietf.org/doc/html/rfc6455#section-6.1
|
|
218
268
|
// https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
|
219
269
|
|
|
220
|
-
if (!isEstablished(this) || isClosing(this)) {
|
|
270
|
+
if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {
|
|
221
271
|
return
|
|
222
272
|
}
|
|
223
273
|
|
|
@@ -234,12 +284,12 @@ class WebSocket extends EventTarget {
|
|
|
234
284
|
// the bufferedAmount attribute by the number of bytes needed to
|
|
235
285
|
// express the argument as UTF-8.
|
|
236
286
|
|
|
237
|
-
const
|
|
287
|
+
const buffer = Buffer.from(data)
|
|
238
288
|
|
|
239
|
-
this.#bufferedAmount +=
|
|
240
|
-
this.#sendQueue.add(
|
|
241
|
-
this.#bufferedAmount -=
|
|
242
|
-
}, sendHints.
|
|
289
|
+
this.#bufferedAmount += buffer.byteLength
|
|
290
|
+
this.#sendQueue.add(buffer, () => {
|
|
291
|
+
this.#bufferedAmount -= buffer.byteLength
|
|
292
|
+
}, sendHints.text)
|
|
243
293
|
} else if (types.isArrayBuffer(data)) {
|
|
244
294
|
// If the WebSocket connection is established, and the WebSocket
|
|
245
295
|
// closing handshake has not yet started, then the user agent must
|
|
@@ -274,7 +324,7 @@ class WebSocket extends EventTarget {
|
|
|
274
324
|
this.#sendQueue.add(data, () => {
|
|
275
325
|
this.#bufferedAmount -= data.byteLength
|
|
276
326
|
}, sendHints.typedArray)
|
|
277
|
-
} else if (
|
|
327
|
+
} else if (data instanceof Blob) {
|
|
278
328
|
// If the WebSocket connection is established, and the WebSocket
|
|
279
329
|
// closing handshake has not yet started, then the user agent must
|
|
280
330
|
// send a WebSocket Message comprised of data using a binary frame
|
|
@@ -297,7 +347,7 @@ class WebSocket extends EventTarget {
|
|
|
297
347
|
webidl.brandCheck(this, WebSocket)
|
|
298
348
|
|
|
299
349
|
// The readyState getter steps are to return this's ready state.
|
|
300
|
-
return this
|
|
350
|
+
return this.#handler.readyState
|
|
301
351
|
}
|
|
302
352
|
|
|
303
353
|
get bufferedAmount () {
|
|
@@ -310,7 +360,7 @@ class WebSocket extends EventTarget {
|
|
|
310
360
|
webidl.brandCheck(this, WebSocket)
|
|
311
361
|
|
|
312
362
|
// The url getter steps are to return this's url, serialized.
|
|
313
|
-
return URLSerializer(this
|
|
363
|
+
return URLSerializer(this.#url)
|
|
314
364
|
}
|
|
315
365
|
|
|
316
366
|
get extensions () {
|
|
@@ -412,16 +462,16 @@ class WebSocket extends EventTarget {
|
|
|
412
462
|
get binaryType () {
|
|
413
463
|
webidl.brandCheck(this, WebSocket)
|
|
414
464
|
|
|
415
|
-
return this
|
|
465
|
+
return this.#binaryType
|
|
416
466
|
}
|
|
417
467
|
|
|
418
468
|
set binaryType (type) {
|
|
419
469
|
webidl.brandCheck(this, WebSocket)
|
|
420
470
|
|
|
421
471
|
if (type !== 'blob' && type !== 'arraybuffer') {
|
|
422
|
-
this
|
|
472
|
+
this.#binaryType = 'blob'
|
|
423
473
|
} else {
|
|
424
|
-
this
|
|
474
|
+
this.#binaryType = type
|
|
425
475
|
}
|
|
426
476
|
}
|
|
427
477
|
|
|
@@ -431,19 +481,17 @@ class WebSocket extends EventTarget {
|
|
|
431
481
|
#onConnectionEstablished (response, parsedExtensions) {
|
|
432
482
|
// processResponse is called when the "response’s header list has been received and initialized."
|
|
433
483
|
// once this happens, the connection is open
|
|
434
|
-
this
|
|
484
|
+
this.#handler.socket = response.socket
|
|
435
485
|
|
|
436
|
-
const parser = new ByteParser(this, parsedExtensions)
|
|
437
|
-
parser.on('drain', onParserDrain)
|
|
438
|
-
parser.on('error', onParserError
|
|
439
|
-
|
|
440
|
-
response.socket.ws = this
|
|
441
|
-
this[kByteParser] = parser
|
|
486
|
+
const parser = new ByteParser(this.#handler, parsedExtensions)
|
|
487
|
+
parser.on('drain', () => this.#handler.onParserDrain())
|
|
488
|
+
parser.on('error', (err) => this.#handler.onParserError(err))
|
|
442
489
|
|
|
490
|
+
this.#parser = parser
|
|
443
491
|
this.#sendQueue = new SendQueue(response.socket)
|
|
444
492
|
|
|
445
493
|
// 1. Change the ready state to OPEN (1).
|
|
446
|
-
this
|
|
494
|
+
this.#handler.readyState = states.OPEN
|
|
447
495
|
|
|
448
496
|
// 2. Change the extensions attribute’s value to the extensions in use, if
|
|
449
497
|
// it is not the null value.
|
|
@@ -466,6 +514,202 @@ class WebSocket extends EventTarget {
|
|
|
466
514
|
// 4. Fire an event named open at the WebSocket object.
|
|
467
515
|
fireEvent('open', this)
|
|
468
516
|
}
|
|
517
|
+
|
|
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
|
+
|
|
527
|
+
if (reason) {
|
|
528
|
+
// TODO: process.nextTick
|
|
529
|
+
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
|
530
|
+
error: new Error(reason),
|
|
531
|
+
message: reason
|
|
532
|
+
})
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
#onMessage (type, data) {
|
|
537
|
+
// 1. If ready state is not OPEN (1), then return.
|
|
538
|
+
if (this.#handler.readyState !== states.OPEN) {
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 2. Let dataForEvent be determined by switching on type and binary type:
|
|
543
|
+
let dataForEvent
|
|
544
|
+
|
|
545
|
+
if (type === opcodes.TEXT) {
|
|
546
|
+
// -> type indicates that the data is Text
|
|
547
|
+
// a new DOMString containing data
|
|
548
|
+
try {
|
|
549
|
+
dataForEvent = utf8Decode(data)
|
|
550
|
+
} catch {
|
|
551
|
+
failWebsocketConnection(this.#handler, 'Received invalid UTF-8 in text frame.')
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
} else if (type === opcodes.BINARY) {
|
|
555
|
+
if (this.#binaryType === 'blob') {
|
|
556
|
+
// -> type indicates that the data is Binary and binary type is "blob"
|
|
557
|
+
// a new Blob object, created in the relevant Realm of the WebSocket
|
|
558
|
+
// object, that represents data as its raw data
|
|
559
|
+
dataForEvent = new Blob([data])
|
|
560
|
+
} else {
|
|
561
|
+
// -> type indicates that the data is Binary and binary type is "arraybuffer"
|
|
562
|
+
// a new ArrayBuffer object, created in the relevant Realm of the
|
|
563
|
+
// WebSocket object, whose contents are data
|
|
564
|
+
dataForEvent = toArrayBuffer(data)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 3. Fire an event named message at the WebSocket object, using MessageEvent,
|
|
569
|
+
// with the origin attribute initialized to the serialization of the WebSocket
|
|
570
|
+
// object’s url's origin, and the data attribute initialized to dataForEvent.
|
|
571
|
+
fireEvent('message', this, createFastMessageEvent, {
|
|
572
|
+
origin: this.#url.origin,
|
|
573
|
+
data: dataForEvent
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
|
|
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
|
+
#onParserDrain () {
|
|
653
|
+
this.#handler.socket.resume()
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|
658
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
|
|
659
|
+
*/
|
|
660
|
+
#onSocketClose () {
|
|
661
|
+
// If the TCP connection was closed after the
|
|
662
|
+
// WebSocket closing handshake was completed, the WebSocket connection
|
|
663
|
+
// is said to have been closed _cleanly_.
|
|
664
|
+
const wasClean = this.#handler.closeState === sentCloseFrameState.SENT && this.#handler.receivedClose
|
|
665
|
+
|
|
666
|
+
let code = 1005
|
|
667
|
+
let reason = ''
|
|
668
|
+
|
|
669
|
+
const result = this.#parser.closingInfo
|
|
670
|
+
|
|
671
|
+
if (result && !result.error) {
|
|
672
|
+
code = result.code ?? 1005
|
|
673
|
+
reason = result.reason
|
|
674
|
+
} else if (!this.#handler.receivedClose) {
|
|
675
|
+
// If _The WebSocket
|
|
676
|
+
// Connection is Closed_ and no Close control frame was received by the
|
|
677
|
+
// endpoint (such as could occur if the underlying transport connection
|
|
678
|
+
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
|
679
|
+
// 1006.
|
|
680
|
+
code = 1006
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// 1. Change the ready state to CLOSED (3).
|
|
684
|
+
this.#handler.readyState = states.CLOSED
|
|
685
|
+
|
|
686
|
+
// 2. If the user agent was required to fail the WebSocket
|
|
687
|
+
// connection, or if the WebSocket connection was closed
|
|
688
|
+
// after being flagged as full, fire an event named error
|
|
689
|
+
// at the WebSocket object.
|
|
690
|
+
// TODO
|
|
691
|
+
|
|
692
|
+
// 3. Fire an event named close at the WebSocket object,
|
|
693
|
+
// using CloseEvent, with the wasClean attribute
|
|
694
|
+
// initialized to true if the connection closed cleanly
|
|
695
|
+
// and false otherwise, the code attribute initialized to
|
|
696
|
+
// the WebSocket connection close code, and the reason
|
|
697
|
+
// attribute initialized to the result of applying UTF-8
|
|
698
|
+
// decode without BOM to the WebSocket connection close
|
|
699
|
+
// reason.
|
|
700
|
+
// TODO: process.nextTick
|
|
701
|
+
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
|
702
|
+
wasClean, code, reason
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
if (channels.close.hasSubscribers) {
|
|
706
|
+
channels.close.publish({
|
|
707
|
+
websocket: this,
|
|
708
|
+
code,
|
|
709
|
+
reason
|
|
710
|
+
})
|
|
711
|
+
}
|
|
712
|
+
}
|
|
469
713
|
}
|
|
470
714
|
|
|
471
715
|
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
|
|
@@ -514,7 +758,7 @@ webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
|
|
|
514
758
|
)
|
|
515
759
|
|
|
516
760
|
webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
|
|
517
|
-
if (webidl.util.Type(V) ===
|
|
761
|
+
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {
|
|
518
762
|
return webidl.converters['sequence<DOMString>'](V)
|
|
519
763
|
}
|
|
520
764
|
|
|
@@ -540,7 +784,7 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
|
|
540
784
|
])
|
|
541
785
|
|
|
542
786
|
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
|
|
543
|
-
if (webidl.util.Type(V) ===
|
|
787
|
+
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {
|
|
544
788
|
return webidl.converters.WebSocketInit(V)
|
|
545
789
|
}
|
|
546
790
|
|
|
@@ -548,39 +792,19 @@ webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = functio
|
|
|
548
792
|
}
|
|
549
793
|
|
|
550
794
|
webidl.converters.WebSocketSendData = function (V) {
|
|
551
|
-
if (webidl.util.Type(V) ===
|
|
552
|
-
if (
|
|
553
|
-
return
|
|
795
|
+
if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
|
|
796
|
+
if (V instanceof Blob) {
|
|
797
|
+
return V
|
|
554
798
|
}
|
|
555
799
|
|
|
556
800
|
if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
|
|
557
|
-
return
|
|
801
|
+
return V
|
|
558
802
|
}
|
|
559
803
|
}
|
|
560
804
|
|
|
561
805
|
return webidl.converters.USVString(V)
|
|
562
806
|
}
|
|
563
807
|
|
|
564
|
-
function onParserDrain () {
|
|
565
|
-
this.ws[kResponse].socket.resume()
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function onParserError (err) {
|
|
569
|
-
let message
|
|
570
|
-
let code
|
|
571
|
-
|
|
572
|
-
if (err instanceof CloseEvent) {
|
|
573
|
-
message = err.reason
|
|
574
|
-
code = err.code
|
|
575
|
-
} else {
|
|
576
|
-
message = err.message
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message }))
|
|
580
|
-
|
|
581
|
-
closeWebSocketConnection(this, code)
|
|
582
|
-
}
|
|
583
|
-
|
|
584
808
|
module.exports = {
|
|
585
809
|
WebSocket
|
|
586
810
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-alpha.1",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -65,19 +65,19 @@
|
|
|
65
65
|
"build:node": "npx esbuild@0.19.10 index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js",
|
|
66
66
|
"prebuild:wasm": "node build/wasm.js --prebuild",
|
|
67
67
|
"build:wasm": "node build/wasm.js --docker",
|
|
68
|
-
"
|
|
69
|
-
"lint
|
|
68
|
+
"generate-pem": "node scripts/generate-pem.js",
|
|
69
|
+
"lint": "eslint --cache",
|
|
70
|
+
"lint:fix": "eslint --fix --cache",
|
|
70
71
|
"test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
|
|
71
|
-
"test:javascript": "
|
|
72
|
-
"test:javascript:
|
|
72
|
+
"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",
|
|
74
|
+
"test:javascript:without-intl": "npm run test:javascript:no-jest",
|
|
73
75
|
"test:busboy": "borp -p \"test/busboy/*.js\"",
|
|
74
76
|
"test:cache": "borp -p \"test/cache/*.js\"",
|
|
75
77
|
"test:cookies": "borp -p \"test/cookie/*.js\"",
|
|
76
|
-
"test:eventsource": "npm run build:node &&
|
|
77
|
-
"test:eventsource:nobuild": "borp --expose-gc -p \"test/eventsource/*.js\"",
|
|
78
|
+
"test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"",
|
|
78
79
|
"test:fuzzing": "node test/fuzzing/fuzzing.test.js",
|
|
79
|
-
"test:fetch": "npm run build:node && npm run test:
|
|
80
|
-
"test:fetch:nobuild": "borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
|
|
80
|
+
"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",
|
|
81
81
|
"test:interceptors": "borp -p \"test/interceptors/*.js\"",
|
|
82
82
|
"test:jest": "cross-env NODE_V8_COVERAGE= jest",
|
|
83
83
|
"test:unit": "borp --expose-gc -p \"test/*.js\"",
|
|
@@ -85,12 +85,12 @@
|
|
|
85
85
|
"test:node-test": "borp -p \"test/node-test/**/*.js\"",
|
|
86
86
|
"test:tdd": "borp --expose-gc -p \"test/*.js\"",
|
|
87
87
|
"test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w",
|
|
88
|
-
"test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types && tsc ./types/*.d.ts --noEmit --typeRoots ./types",
|
|
88
|
+
"test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types",
|
|
89
89
|
"test:webidl": "borp -p \"test/webidl/*.js\"",
|
|
90
90
|
"test:websocket": "borp -p \"test/websocket/*.js\"",
|
|
91
91
|
"test:websocket:autobahn": "node test/autobahn/client.js",
|
|
92
92
|
"test:websocket:autobahn:report": "node test/autobahn/report.js",
|
|
93
|
-
"test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-
|
|
93
|
+
"test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
|
|
94
94
|
"test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
|
|
95
95
|
"coverage": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report",
|
|
96
96
|
"coverage:ci": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci",
|
|
@@ -102,27 +102,24 @@
|
|
|
102
102
|
"prepare": "husky && node ./scripts/platform-shell.js"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
|
-
"@fastify/busboy": "
|
|
105
|
+
"@fastify/busboy": "3.0.0",
|
|
106
106
|
"@matteo.collina/tspl": "^0.1.1",
|
|
107
107
|
"@sinonjs/fake-timers": "^11.1.0",
|
|
108
|
-
"@types/node": "
|
|
108
|
+
"@types/node": "~18.17.19",
|
|
109
109
|
"abort-controller": "^3.0.0",
|
|
110
|
-
"borp": "^0.
|
|
110
|
+
"borp": "^0.17.0",
|
|
111
111
|
"c8": "^10.0.0",
|
|
112
112
|
"cross-env": "^7.0.3",
|
|
113
113
|
"dns-packet": "^5.4.0",
|
|
114
|
+
"eslint": "^9.9.0",
|
|
114
115
|
"fast-check": "^3.17.1",
|
|
115
|
-
"form-data": "^4.0.0",
|
|
116
|
-
"formdata-node": "^6.0.3",
|
|
117
116
|
"https-pem": "^3.0.0",
|
|
118
117
|
"husky": "^9.0.7",
|
|
119
118
|
"jest": "^29.0.2",
|
|
120
|
-
"
|
|
119
|
+
"neostandard": "^0.11.2",
|
|
121
120
|
"node-forge": "^1.3.1",
|
|
122
121
|
"pre-commit": "^1.2.2",
|
|
123
122
|
"proxy": "^2.1.1",
|
|
124
|
-
"snazzy": "^9.0.0",
|
|
125
|
-
"standard": "^17.0.0",
|
|
126
123
|
"tsd": "^0.31.0",
|
|
127
124
|
"typescript": "^5.0.2",
|
|
128
125
|
"ws": "^8.11.0"
|
|
@@ -130,16 +127,6 @@
|
|
|
130
127
|
"engines": {
|
|
131
128
|
"node": ">=18.17"
|
|
132
129
|
},
|
|
133
|
-
"standard": {
|
|
134
|
-
"env": [
|
|
135
|
-
"jest"
|
|
136
|
-
],
|
|
137
|
-
"ignore": [
|
|
138
|
-
"lib/llhttp/constants.js",
|
|
139
|
-
"lib/llhttp/utils.js",
|
|
140
|
-
"test/fixtures/wpt"
|
|
141
|
-
]
|
|
142
|
-
},
|
|
143
130
|
"tsd": {
|
|
144
131
|
"directory": "test/types",
|
|
145
132
|
"compilerOptions": {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const { readFileSync, writeFileSync } = require('node:fs')
|
|
4
4
|
const { transcode } = require('node:buffer')
|
|
5
5
|
|
|
6
|
-
const buffer = transcode
|
|
6
|
+
const buffer = transcode
|
|
7
|
+
? transcode(readFileSync('./undici-fetch.js'), 'utf8', 'latin1')
|
|
8
|
+
: readFileSync('./undici-fetch.js')
|
|
7
9
|
|
|
8
10
|
writeFileSync('./undici-fetch.js', buffer.toString('latin1'))
|