undici 6.20.0 → 7.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -10
- package/docs/docs/api/Agent.md +0 -3
- package/docs/docs/api/Client.md +1 -3
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +60 -8
- package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
- package/docs/docs/api/Fetch.md +1 -0
- package/docs/docs/api/MockAgent.md +2 -0
- package/docs/docs/api/MockPool.md +2 -1
- package/docs/docs/api/Pool.md +0 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/docs/docs/api/WebSocket.md +45 -3
- package/index.js +6 -6
- 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 +32 -47
- package/lib/api/api-stream.js +39 -50
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +261 -64
- package/lib/api/util.js +2 -0
- package/lib/core/constants.js +11 -9
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +4 -4
- package/lib/core/request.js +11 -9
- package/lib/core/symbols.js +2 -1
- package/lib/core/tree.js +9 -1
- package/lib/core/util.js +219 -48
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +278 -54
- package/lib/dispatcher/client-h2.js +1 -1
- package/lib/dispatcher/client.js +23 -34
- package/lib/dispatcher/dispatcher-base.js +2 -34
- package/lib/dispatcher/dispatcher.js +3 -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 +6 -7
- package/lib/handler/decorator-handler.js +24 -0
- package/lib/handler/redirect-handler.js +11 -2
- package/lib/handler/retry-handler.js +12 -3
- package/lib/interceptor/dns.js +346 -0
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +4 -1
- 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/web/cache/cache.js +24 -21
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +17 -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 +42 -36
- package/lib/web/fetch/constants.js +35 -26
- package/lib/web/fetch/data-url.js +1 -1
- package/lib/web/fetch/formdata-parser.js +2 -2
- package/lib/web/fetch/formdata.js +65 -54
- package/lib/web/fetch/headers.js +117 -85
- package/lib/web/fetch/index.js +55 -62
- package/lib/web/fetch/request.js +135 -77
- package/lib/web/fetch/response.js +86 -56
- package/lib/web/fetch/util.js +90 -64
- package/lib/web/fetch/webidl.js +99 -64
- package/lib/web/websocket/connection.js +76 -147
- package/lib/web/websocket/constants.js +3 -4
- 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 +20 -33
- 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/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 +22 -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 +22 -14
- 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 +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
|
@@ -3,30 +3,44 @@
|
|
|
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 } = 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
|
+
getURLRecord
|
|
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 { channels } = require('../../core/diagnostics')
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} Handler
|
|
29
|
+
* @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
|
|
30
|
+
* @property {(code: number, reason: any) => void} onFail
|
|
31
|
+
* @property {(opcode: number, data: Buffer) => void} onMessage
|
|
32
|
+
* @property {(error: Error) => void} onParserError
|
|
33
|
+
* @property {() => void} onParserDrain
|
|
34
|
+
* @property {(chunk: Buffer) => void} onSocketData
|
|
35
|
+
* @property {(err: Error) => void} onSocketError
|
|
36
|
+
* @property {() => void} onSocketClose
|
|
37
|
+
*
|
|
38
|
+
* @property {number} readyState
|
|
39
|
+
* @property {import('stream').Duplex} socket
|
|
40
|
+
* @property {Set<number>} closeState
|
|
41
|
+
* @property {import('../fetch/index').Fetch} controller
|
|
42
|
+
* @property {boolean} [wasEverConnected=false]
|
|
43
|
+
*/
|
|
30
44
|
|
|
31
45
|
// https://websockets.spec.whatwg.org/#interface-definition
|
|
32
46
|
class WebSocket extends EventTarget {
|
|
@@ -44,6 +58,41 @@ class WebSocket extends EventTarget {
|
|
|
44
58
|
/** @type {SendQueue} */
|
|
45
59
|
#sendQueue
|
|
46
60
|
|
|
61
|
+
/** @type {Handler} */
|
|
62
|
+
#handler = {
|
|
63
|
+
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
|
64
|
+
onFail: (code, reason) => this.#onFail(code, reason),
|
|
65
|
+
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
|
66
|
+
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
|
|
67
|
+
onParserDrain: () => this.#onParserDrain(),
|
|
68
|
+
onSocketData: (chunk) => {
|
|
69
|
+
if (!this.#parser.write(chunk)) {
|
|
70
|
+
this.#handler.socket.pause()
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onSocketError: (err) => {
|
|
74
|
+
this.#handler.readyState = states.CLOSING
|
|
75
|
+
|
|
76
|
+
if (channels.socketError.hasSubscribers) {
|
|
77
|
+
channels.socketError.publish(err)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.#handler.socket.destroy()
|
|
81
|
+
},
|
|
82
|
+
onSocketClose: () => this.#onSocketClose(),
|
|
83
|
+
|
|
84
|
+
readyState: states.CONNECTING,
|
|
85
|
+
socket: null,
|
|
86
|
+
closeState: new Set(),
|
|
87
|
+
controller: null,
|
|
88
|
+
wasEverConnected: false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#url
|
|
92
|
+
#binaryType
|
|
93
|
+
/** @type {import('./receiver').ByteParser} */
|
|
94
|
+
#parser
|
|
95
|
+
|
|
47
96
|
/**
|
|
48
97
|
* @param {string} url
|
|
49
98
|
* @param {string|string[]} protocols
|
|
@@ -56,51 +105,22 @@ class WebSocket extends EventTarget {
|
|
|
56
105
|
|
|
57
106
|
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
|
|
58
107
|
|
|
59
|
-
url = webidl.converters.USVString(url
|
|
108
|
+
url = webidl.converters.USVString(url)
|
|
60
109
|
protocols = options.protocols
|
|
61
110
|
|
|
62
111
|
// 1. Let baseURL be this's relevant settings object's API base URL.
|
|
63
112
|
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
|
64
113
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
urlRecord = new URL(url, baseURL)
|
|
70
|
-
} catch (e) {
|
|
71
|
-
// 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
|
|
72
|
-
throw new DOMException(e, 'SyntaxError')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws".
|
|
76
|
-
if (urlRecord.protocol === 'http:') {
|
|
77
|
-
urlRecord.protocol = 'ws:'
|
|
78
|
-
} else if (urlRecord.protocol === 'https:') {
|
|
79
|
-
// 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss".
|
|
80
|
-
urlRecord.protocol = 'wss:'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
|
|
84
|
-
if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
|
|
85
|
-
throw new DOMException(
|
|
86
|
-
`Expected a ws: or wss: protocol, got ${urlRecord.protocol}`,
|
|
87
|
-
'SyntaxError'
|
|
88
|
-
)
|
|
89
|
-
}
|
|
114
|
+
// 2. Let urlRecord be the result of getting a URL record given url and baseURL.
|
|
115
|
+
const urlRecord = getURLRecord(url, baseURL)
|
|
90
116
|
|
|
91
|
-
//
|
|
92
|
-
// DOMException.
|
|
93
|
-
if (urlRecord.hash || urlRecord.href.endsWith('#')) {
|
|
94
|
-
throw new DOMException('Got fragment', 'SyntaxError')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 8. If protocols is a string, set protocols to a sequence consisting
|
|
117
|
+
// 3. If protocols is a string, set protocols to a sequence consisting
|
|
98
118
|
// of just that string.
|
|
99
119
|
if (typeof protocols === 'string') {
|
|
100
120
|
protocols = [protocols]
|
|
101
121
|
}
|
|
102
122
|
|
|
103
|
-
//
|
|
123
|
+
// 4. If any of the values in protocols occur more than once or otherwise
|
|
104
124
|
// fail to match the requirements for elements that comprise the value
|
|
105
125
|
// of `Sec-WebSocket-Protocol` fields as defined by The WebSocket
|
|
106
126
|
// protocol, then throw a "SyntaxError" DOMException.
|
|
@@ -112,31 +132,27 @@ class WebSocket extends EventTarget {
|
|
|
112
132
|
throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
|
|
113
133
|
}
|
|
114
134
|
|
|
115
|
-
//
|
|
116
|
-
this
|
|
135
|
+
// 5. Set this's url to urlRecord.
|
|
136
|
+
this.#url = new URL(urlRecord.href)
|
|
117
137
|
|
|
118
|
-
//
|
|
138
|
+
// 6. Let client be this's relevant settings object.
|
|
119
139
|
const client = environmentSettingsObject.settingsObject
|
|
120
140
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
this[kController] = establishWebSocketConnection(
|
|
141
|
+
// 7. Run this step in parallel:
|
|
142
|
+
// 7.1. Establish a WebSocket connection given urlRecord, protocols,
|
|
143
|
+
// and client.
|
|
144
|
+
this.#handler.controller = establishWebSocketConnection(
|
|
126
145
|
urlRecord,
|
|
127
146
|
protocols,
|
|
128
147
|
client,
|
|
129
|
-
this,
|
|
130
|
-
(response, extensions) => this.#onConnectionEstablished(response, extensions),
|
|
148
|
+
this.#handler,
|
|
131
149
|
options
|
|
132
150
|
)
|
|
133
151
|
|
|
134
152
|
// Each WebSocket object has an associated ready state, which is a
|
|
135
153
|
// number representing the state of the connection. Initially it must
|
|
136
154
|
// be CONNECTING (0).
|
|
137
|
-
this
|
|
138
|
-
|
|
139
|
-
this[kSentClose] = sentCloseFrameState.NOT_SENT
|
|
155
|
+
this.#handler.readyState = WebSocket.CONNECTING
|
|
140
156
|
|
|
141
157
|
// The extensions attribute must initially return the empty string.
|
|
142
158
|
|
|
@@ -144,7 +160,7 @@ class WebSocket extends EventTarget {
|
|
|
144
160
|
|
|
145
161
|
// Each WebSocket object has an associated binary type, which is a
|
|
146
162
|
// BinaryType. Initially it must be "blob".
|
|
147
|
-
this
|
|
163
|
+
this.#binaryType = 'blob'
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
/**
|
|
@@ -162,37 +178,17 @@ class WebSocket extends EventTarget {
|
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
if (reason !== undefined) {
|
|
165
|
-
reason = webidl.converters.USVString(reason
|
|
181
|
+
reason = webidl.converters.USVString(reason)
|
|
166
182
|
}
|
|
167
183
|
|
|
168
|
-
// 1. If code is
|
|
169
|
-
|
|
170
|
-
// "InvalidAccessError" DOMException.
|
|
171
|
-
if (code !== undefined) {
|
|
172
|
-
if (code !== 1000 && (code < 3000 || code > 4999)) {
|
|
173
|
-
throw new DOMException('invalid code', 'InvalidAccessError')
|
|
174
|
-
}
|
|
175
|
-
}
|
|
184
|
+
// 1. If code is the special value "missing", then set code to null.
|
|
185
|
+
code ??= null
|
|
176
186
|
|
|
177
|
-
|
|
187
|
+
// 2. If reason is the special value "missing", then set reason to the empty string.
|
|
188
|
+
reason ??= ''
|
|
178
189
|
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
// 1. Let reasonBytes be the result of encoding reason.
|
|
182
|
-
// 2. If reasonBytes is longer than 123 bytes, then throw a
|
|
183
|
-
// "SyntaxError" DOMException.
|
|
184
|
-
reasonByteLength = Buffer.byteLength(reason)
|
|
185
|
-
|
|
186
|
-
if (reasonByteLength > 123) {
|
|
187
|
-
throw new DOMException(
|
|
188
|
-
`Reason must be less than 123 bytes; received ${reasonByteLength}`,
|
|
189
|
-
'SyntaxError'
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 3. Run the first matching steps from the following list:
|
|
195
|
-
closeWebSocketConnection(this, code, reason, reasonByteLength)
|
|
190
|
+
// 3. Close the WebSocket with this, code, and reason.
|
|
191
|
+
closeWebSocketConnection(this.#handler, code, reason, true)
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
/**
|
|
@@ -209,7 +205,7 @@ class WebSocket extends EventTarget {
|
|
|
209
205
|
|
|
210
206
|
// 1. If this's ready state is CONNECTING, then throw an
|
|
211
207
|
// "InvalidStateError" DOMException.
|
|
212
|
-
if (isConnecting(this)) {
|
|
208
|
+
if (isConnecting(this.#handler.readyState)) {
|
|
213
209
|
throw new DOMException('Sent before connected.', 'InvalidStateError')
|
|
214
210
|
}
|
|
215
211
|
|
|
@@ -217,7 +213,7 @@ class WebSocket extends EventTarget {
|
|
|
217
213
|
// https://datatracker.ietf.org/doc/html/rfc6455#section-6.1
|
|
218
214
|
// https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
|
219
215
|
|
|
220
|
-
if (!isEstablished(this) || isClosing(this)) {
|
|
216
|
+
if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {
|
|
221
217
|
return
|
|
222
218
|
}
|
|
223
219
|
|
|
@@ -234,12 +230,12 @@ class WebSocket extends EventTarget {
|
|
|
234
230
|
// the bufferedAmount attribute by the number of bytes needed to
|
|
235
231
|
// express the argument as UTF-8.
|
|
236
232
|
|
|
237
|
-
const
|
|
233
|
+
const buffer = Buffer.from(data)
|
|
238
234
|
|
|
239
|
-
this.#bufferedAmount +=
|
|
240
|
-
this.#sendQueue.add(
|
|
241
|
-
this.#bufferedAmount -=
|
|
242
|
-
}, sendHints.
|
|
235
|
+
this.#bufferedAmount += buffer.byteLength
|
|
236
|
+
this.#sendQueue.add(buffer, () => {
|
|
237
|
+
this.#bufferedAmount -= buffer.byteLength
|
|
238
|
+
}, sendHints.text)
|
|
243
239
|
} else if (types.isArrayBuffer(data)) {
|
|
244
240
|
// If the WebSocket connection is established, and the WebSocket
|
|
245
241
|
// closing handshake has not yet started, then the user agent must
|
|
@@ -274,7 +270,7 @@ class WebSocket extends EventTarget {
|
|
|
274
270
|
this.#sendQueue.add(data, () => {
|
|
275
271
|
this.#bufferedAmount -= data.byteLength
|
|
276
272
|
}, sendHints.typedArray)
|
|
277
|
-
} else if (
|
|
273
|
+
} else if (webidl.is.Blob(data)) {
|
|
278
274
|
// If the WebSocket connection is established, and the WebSocket
|
|
279
275
|
// closing handshake has not yet started, then the user agent must
|
|
280
276
|
// send a WebSocket Message comprised of data using a binary frame
|
|
@@ -297,7 +293,7 @@ class WebSocket extends EventTarget {
|
|
|
297
293
|
webidl.brandCheck(this, WebSocket)
|
|
298
294
|
|
|
299
295
|
// The readyState getter steps are to return this's ready state.
|
|
300
|
-
return this
|
|
296
|
+
return this.#handler.readyState
|
|
301
297
|
}
|
|
302
298
|
|
|
303
299
|
get bufferedAmount () {
|
|
@@ -310,7 +306,7 @@ class WebSocket extends EventTarget {
|
|
|
310
306
|
webidl.brandCheck(this, WebSocket)
|
|
311
307
|
|
|
312
308
|
// The url getter steps are to return this's url, serialized.
|
|
313
|
-
return URLSerializer(this
|
|
309
|
+
return URLSerializer(this.#url)
|
|
314
310
|
}
|
|
315
311
|
|
|
316
312
|
get extensions () {
|
|
@@ -412,16 +408,16 @@ class WebSocket extends EventTarget {
|
|
|
412
408
|
get binaryType () {
|
|
413
409
|
webidl.brandCheck(this, WebSocket)
|
|
414
410
|
|
|
415
|
-
return this
|
|
411
|
+
return this.#binaryType
|
|
416
412
|
}
|
|
417
413
|
|
|
418
414
|
set binaryType (type) {
|
|
419
415
|
webidl.brandCheck(this, WebSocket)
|
|
420
416
|
|
|
421
417
|
if (type !== 'blob' && type !== 'arraybuffer') {
|
|
422
|
-
this
|
|
418
|
+
this.#binaryType = 'blob'
|
|
423
419
|
} else {
|
|
424
|
-
this
|
|
420
|
+
this.#binaryType = type
|
|
425
421
|
}
|
|
426
422
|
}
|
|
427
423
|
|
|
@@ -431,19 +427,17 @@ class WebSocket extends EventTarget {
|
|
|
431
427
|
#onConnectionEstablished (response, parsedExtensions) {
|
|
432
428
|
// processResponse is called when the "response’s header list has been received and initialized."
|
|
433
429
|
// once this happens, the connection is open
|
|
434
|
-
this
|
|
435
|
-
|
|
436
|
-
const parser = new ByteParser(this, parsedExtensions)
|
|
437
|
-
parser.on('drain', onParserDrain)
|
|
438
|
-
parser.on('error', onParserError.bind(this))
|
|
430
|
+
this.#handler.socket = response.socket
|
|
439
431
|
|
|
440
|
-
|
|
441
|
-
|
|
432
|
+
const parser = new ByteParser(this.#handler, parsedExtensions)
|
|
433
|
+
parser.on('drain', () => this.#handler.onParserDrain())
|
|
434
|
+
parser.on('error', (err) => this.#handler.onParserError(err))
|
|
442
435
|
|
|
436
|
+
this.#parser = parser
|
|
443
437
|
this.#sendQueue = new SendQueue(response.socket)
|
|
444
438
|
|
|
445
439
|
// 1. Change the ready state to OPEN (1).
|
|
446
|
-
this
|
|
440
|
+
this.#handler.readyState = states.OPEN
|
|
447
441
|
|
|
448
442
|
// 2. Change the extensions attribute’s value to the extensions in use, if
|
|
449
443
|
// it is not the null value.
|
|
@@ -466,6 +460,131 @@ class WebSocket extends EventTarget {
|
|
|
466
460
|
// 4. Fire an event named open at the WebSocket object.
|
|
467
461
|
fireEvent('open', this)
|
|
468
462
|
}
|
|
463
|
+
|
|
464
|
+
#onFail (code, reason) {
|
|
465
|
+
if (reason) {
|
|
466
|
+
// TODO: process.nextTick
|
|
467
|
+
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
|
468
|
+
error: new Error(reason),
|
|
469
|
+
message: reason
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!this.#handler.wasEverConnected) {
|
|
474
|
+
this.#handler.readyState = states.CLOSED
|
|
475
|
+
|
|
476
|
+
// If the WebSocket connection could not be established, it is also said
|
|
477
|
+
// that _The WebSocket Connection is Closed_, but not _cleanly_.
|
|
478
|
+
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
|
479
|
+
wasClean: false, code, reason
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#onMessage (type, data) {
|
|
485
|
+
// 1. If ready state is not OPEN (1), then return.
|
|
486
|
+
if (this.#handler.readyState !== states.OPEN) {
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 2. Let dataForEvent be determined by switching on type and binary type:
|
|
491
|
+
let dataForEvent
|
|
492
|
+
|
|
493
|
+
if (type === opcodes.TEXT) {
|
|
494
|
+
// -> type indicates that the data is Text
|
|
495
|
+
// a new DOMString containing data
|
|
496
|
+
try {
|
|
497
|
+
dataForEvent = utf8Decode(data)
|
|
498
|
+
} catch {
|
|
499
|
+
failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')
|
|
500
|
+
return
|
|
501
|
+
}
|
|
502
|
+
} else if (type === opcodes.BINARY) {
|
|
503
|
+
if (this.#binaryType === 'blob') {
|
|
504
|
+
// -> type indicates that the data is Binary and binary type is "blob"
|
|
505
|
+
// a new Blob object, created in the relevant Realm of the WebSocket
|
|
506
|
+
// object, that represents data as its raw data
|
|
507
|
+
dataForEvent = new Blob([data])
|
|
508
|
+
} else {
|
|
509
|
+
// -> type indicates that the data is Binary and binary type is "arraybuffer"
|
|
510
|
+
// a new ArrayBuffer object, created in the relevant Realm of the
|
|
511
|
+
// WebSocket object, whose contents are data
|
|
512
|
+
dataForEvent = toArrayBuffer(data)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 3. Fire an event named message at the WebSocket object, using MessageEvent,
|
|
517
|
+
// with the origin attribute initialized to the serialization of the WebSocket
|
|
518
|
+
// object’s url's origin, and the data attribute initialized to dataForEvent.
|
|
519
|
+
fireEvent('message', this, createFastMessageEvent, {
|
|
520
|
+
origin: this.#url.origin,
|
|
521
|
+
data: dataForEvent
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
#onParserDrain () {
|
|
526
|
+
this.#handler.socket.resume()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|
531
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
|
|
532
|
+
*/
|
|
533
|
+
#onSocketClose () {
|
|
534
|
+
// If the TCP connection was closed after the
|
|
535
|
+
// WebSocket closing handshake was completed, the WebSocket connection
|
|
536
|
+
// is said to have been closed _cleanly_.
|
|
537
|
+
const wasClean =
|
|
538
|
+
this.#handler.closeState.has(sentCloseFrameState.SENT) &&
|
|
539
|
+
this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
|
|
540
|
+
|
|
541
|
+
let code = 1005
|
|
542
|
+
let reason = ''
|
|
543
|
+
|
|
544
|
+
const result = this.#parser.closingInfo
|
|
545
|
+
|
|
546
|
+
if (result && !result.error) {
|
|
547
|
+
code = result.code ?? 1005
|
|
548
|
+
reason = result.reason
|
|
549
|
+
} else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
|
|
550
|
+
// If _The WebSocket
|
|
551
|
+
// Connection is Closed_ and no Close control frame was received by the
|
|
552
|
+
// endpoint (such as could occur if the underlying transport connection
|
|
553
|
+
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
|
554
|
+
// 1006.
|
|
555
|
+
code = 1006
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 1. Change the ready state to CLOSED (3).
|
|
559
|
+
this.#handler.readyState = states.CLOSED
|
|
560
|
+
|
|
561
|
+
// 2. If the user agent was required to fail the WebSocket
|
|
562
|
+
// connection, or if the WebSocket connection was closed
|
|
563
|
+
// after being flagged as full, fire an event named error
|
|
564
|
+
// at the WebSocket object.
|
|
565
|
+
// TODO
|
|
566
|
+
|
|
567
|
+
// 3. Fire an event named close at the WebSocket object,
|
|
568
|
+
// using CloseEvent, with the wasClean attribute
|
|
569
|
+
// initialized to true if the connection closed cleanly
|
|
570
|
+
// and false otherwise, the code attribute initialized to
|
|
571
|
+
// the WebSocket connection close code, and the reason
|
|
572
|
+
// attribute initialized to the result of applying UTF-8
|
|
573
|
+
// decode without BOM to the WebSocket connection close
|
|
574
|
+
// reason.
|
|
575
|
+
// TODO: process.nextTick
|
|
576
|
+
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
|
|
577
|
+
wasClean, code, reason
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
if (channels.close.hasSubscribers) {
|
|
581
|
+
channels.close.publish({
|
|
582
|
+
websocket: this,
|
|
583
|
+
code,
|
|
584
|
+
reason
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
}
|
|
469
588
|
}
|
|
470
589
|
|
|
471
590
|
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
|
|
@@ -514,7 +633,7 @@ webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
|
|
|
514
633
|
)
|
|
515
634
|
|
|
516
635
|
webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
|
|
517
|
-
if (webidl.util.Type(V) ===
|
|
636
|
+
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {
|
|
518
637
|
return webidl.converters['sequence<DOMString>'](V)
|
|
519
638
|
}
|
|
520
639
|
|
|
@@ -540,7 +659,7 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
|
|
540
659
|
])
|
|
541
660
|
|
|
542
661
|
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
|
|
543
|
-
if (webidl.util.Type(V) ===
|
|
662
|
+
if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {
|
|
544
663
|
return webidl.converters.WebSocketInit(V)
|
|
545
664
|
}
|
|
546
665
|
|
|
@@ -548,39 +667,19 @@ webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = functio
|
|
|
548
667
|
}
|
|
549
668
|
|
|
550
669
|
webidl.converters.WebSocketSendData = function (V) {
|
|
551
|
-
if (webidl.util.Type(V) ===
|
|
552
|
-
if (
|
|
553
|
-
return
|
|
670
|
+
if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
|
|
671
|
+
if (webidl.is.Blob(V)) {
|
|
672
|
+
return V
|
|
554
673
|
}
|
|
555
674
|
|
|
556
675
|
if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
|
|
557
|
-
return
|
|
676
|
+
return V
|
|
558
677
|
}
|
|
559
678
|
}
|
|
560
679
|
|
|
561
680
|
return webidl.converters.USVString(V)
|
|
562
681
|
}
|
|
563
682
|
|
|
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
683
|
module.exports = {
|
|
585
684
|
WebSocket
|
|
586
685
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-alpha.2",
|
|
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,22 @@
|
|
|
62
62
|
"main": "index.js",
|
|
63
63
|
"types": "index.d.ts",
|
|
64
64
|
"scripts": {
|
|
65
|
-
"build:node": "
|
|
65
|
+
"build:node": "esbuild 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,44 +102,31 @@
|
|
|
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.19.50",
|
|
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
|
+
"esbuild": "^0.24.0",
|
|
115
|
+
"eslint": "^9.9.0",
|
|
114
116
|
"fast-check": "^3.17.1",
|
|
115
|
-
"form-data": "^4.0.0",
|
|
116
|
-
"formdata-node": "^6.0.3",
|
|
117
117
|
"https-pem": "^3.0.0",
|
|
118
118
|
"husky": "^9.0.7",
|
|
119
119
|
"jest": "^29.0.2",
|
|
120
|
-
"
|
|
120
|
+
"neostandard": "^0.11.2",
|
|
121
121
|
"node-forge": "^1.3.1",
|
|
122
|
-
"pre-commit": "^1.2.2",
|
|
123
122
|
"proxy": "^2.1.1",
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"tsd": "^0.31.0",
|
|
127
|
-
"typescript": "^5.0.2",
|
|
123
|
+
"tsd": "^0.31.2",
|
|
124
|
+
"typescript": "^5.6.2",
|
|
128
125
|
"ws": "^8.11.0"
|
|
129
126
|
},
|
|
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'))
|