undici 6.15.0 → 6.16.0
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/docs/docs/api/Util.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/lib/api/api-request.js +40 -17
- package/lib/api/readable.js +6 -2
- package/lib/dispatcher/client-h2.js +2 -0
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/headers.js +5 -1
- package/lib/web/fetch/index.js +35 -23
- package/lib/web/fetch/request.js +41 -43
- package/lib/web/fetch/response.js +19 -0
- package/lib/web/fetch/webidl.js +2 -1
- package/lib/web/websocket/connection.js +75 -6
- package/lib/web/websocket/frame.js +30 -8
- package/lib/web/websocket/receiver.js +18 -4
- package/lib/web/websocket/util.js +10 -2
- package/lib/web/websocket/websocket.js +23 -73
- package/package.json +3 -3
- package/types/fetch.d.ts +2 -1
- package/types/mock-interceptor.d.ts +4 -4
- package/types/util.d.ts +4 -17
package/docs/docs/api/Util.md
CHANGED
|
@@ -8,11 +8,11 @@ Receives a header object and returns the parsed value.
|
|
|
8
8
|
|
|
9
9
|
Arguments:
|
|
10
10
|
|
|
11
|
-
- **headers** `
|
|
11
|
+
- **headers** `(Buffer | string | (Buffer | string)[])[]` (required) - Header object.
|
|
12
12
|
|
|
13
13
|
- **obj** `Record<string, string | string[]>` (optional) - Object to specify a proxy object. The parsed value is assigned to this object. But, if **headers** is an object, it is not used.
|
|
14
14
|
|
|
15
|
-
Returns: `Record<string, string | string[]>` If **
|
|
15
|
+
Returns: `Record<string, string | string[]>` If **obj** is specified, it is equivalent to **obj**.
|
|
16
16
|
|
|
17
17
|
## `headerNameToString(value)`
|
|
18
18
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Connecting through a proxy is possible by:
|
|
4
4
|
|
|
5
|
-
- Using [
|
|
5
|
+
- Using [ProxyAgent](../api/ProxyAgent.md).
|
|
6
6
|
- Configuring `Client` or `Pool` constructor.
|
|
7
7
|
|
|
8
8
|
The proxy url should be passed to the `Client` or `Pool` constructor, while the upstream server url
|
package/lib/api/api-request.js
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
const { Readable } = require('./readable')
|
|
5
|
-
const { InvalidArgumentError } = require('../core/errors')
|
|
5
|
+
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
|
|
6
6
|
const util = require('../core/util')
|
|
7
7
|
const { getResolveErrorBodyCallback } = require('./util')
|
|
8
8
|
const { AsyncResource } = require('node:async_hooks')
|
|
9
|
-
const { addSignal, removeSignal } = require('./abort-signal')
|
|
10
9
|
|
|
11
10
|
class RequestHandler extends AsyncResource {
|
|
12
11
|
constructor (opts, callback) {
|
|
@@ -56,6 +55,9 @@ class RequestHandler extends AsyncResource {
|
|
|
56
55
|
this.onInfo = onInfo || null
|
|
57
56
|
this.throwOnError = throwOnError
|
|
58
57
|
this.highWaterMark = highWaterMark
|
|
58
|
+
this.signal = signal
|
|
59
|
+
this.reason = null
|
|
60
|
+
this.removeAbortListener = null
|
|
59
61
|
|
|
60
62
|
if (util.isStream(body)) {
|
|
61
63
|
body.on('error', (err) => {
|
|
@@ -63,7 +65,26 @@ class RequestHandler extends AsyncResource {
|
|
|
63
65
|
})
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
if (this.signal) {
|
|
69
|
+
if (this.signal.aborted) {
|
|
70
|
+
this.reason = this.signal.reason ?? new RequestAbortedError()
|
|
71
|
+
} else {
|
|
72
|
+
this.removeAbortListener = util.addAbortListener(this.signal, () => {
|
|
73
|
+
this.reason = this.signal.reason ?? new RequestAbortedError()
|
|
74
|
+
if (this.res) {
|
|
75
|
+
util.destroy(this.res, this.reason)
|
|
76
|
+
} else if (this.abort) {
|
|
77
|
+
this.abort(this.reason)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (this.removeAbortListener) {
|
|
81
|
+
this.res?.off('close', this.removeAbortListener)
|
|
82
|
+
this.removeAbortListener()
|
|
83
|
+
this.removeAbortListener = null
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
onConnect (abort, context) {
|
|
@@ -93,14 +114,18 @@ class RequestHandler extends AsyncResource {
|
|
|
93
114
|
const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers
|
|
94
115
|
const contentType = parsedHeaders['content-type']
|
|
95
116
|
const contentLength = parsedHeaders['content-length']
|
|
96
|
-
const
|
|
117
|
+
const res = new Readable({ resume, abort, contentType, contentLength, highWaterMark })
|
|
118
|
+
|
|
119
|
+
if (this.removeAbortListener) {
|
|
120
|
+
res.on('close', this.removeAbortListener)
|
|
121
|
+
}
|
|
97
122
|
|
|
98
123
|
this.callback = null
|
|
99
|
-
this.res =
|
|
124
|
+
this.res = res
|
|
100
125
|
if (callback !== null) {
|
|
101
126
|
if (this.throwOnError && statusCode >= 400) {
|
|
102
127
|
this.runInAsyncScope(getResolveErrorBodyCallback, null,
|
|
103
|
-
{ callback, body, contentType, statusCode, statusMessage, headers }
|
|
128
|
+
{ callback, body: res, contentType, statusCode, statusMessage, headers }
|
|
104
129
|
)
|
|
105
130
|
} else {
|
|
106
131
|
this.runInAsyncScope(callback, null, null, {
|
|
@@ -108,7 +133,7 @@ class RequestHandler extends AsyncResource {
|
|
|
108
133
|
headers,
|
|
109
134
|
trailers: this.trailers,
|
|
110
135
|
opaque,
|
|
111
|
-
body,
|
|
136
|
+
body: res,
|
|
112
137
|
context
|
|
113
138
|
})
|
|
114
139
|
}
|
|
@@ -116,25 +141,17 @@ class RequestHandler extends AsyncResource {
|
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
onData (chunk) {
|
|
119
|
-
|
|
120
|
-
return res.push(chunk)
|
|
144
|
+
return this.res.push(chunk)
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
onComplete (trailers) {
|
|
124
|
-
const { res } = this
|
|
125
|
-
|
|
126
|
-
removeSignal(this)
|
|
127
|
-
|
|
128
148
|
util.parseHeaders(trailers, this.trailers)
|
|
129
|
-
|
|
130
|
-
res.push(null)
|
|
149
|
+
this.res.push(null)
|
|
131
150
|
}
|
|
132
151
|
|
|
133
152
|
onError (err) {
|
|
134
153
|
const { res, callback, body, opaque } = this
|
|
135
154
|
|
|
136
|
-
removeSignal(this)
|
|
137
|
-
|
|
138
155
|
if (callback) {
|
|
139
156
|
// TODO: Does this need queueMicrotask?
|
|
140
157
|
this.callback = null
|
|
@@ -155,6 +172,12 @@ class RequestHandler extends AsyncResource {
|
|
|
155
172
|
this.body = null
|
|
156
173
|
util.destroy(body, err)
|
|
157
174
|
}
|
|
175
|
+
|
|
176
|
+
if (this.removeAbortListener) {
|
|
177
|
+
res?.off('close', this.removeAbortListener)
|
|
178
|
+
this.removeAbortListener()
|
|
179
|
+
this.removeAbortListener = null
|
|
180
|
+
}
|
|
158
181
|
}
|
|
159
182
|
}
|
|
160
183
|
|
package/lib/api/readable.js
CHANGED
|
@@ -63,9 +63,13 @@ class BodyReadable extends Readable {
|
|
|
63
63
|
// tick as it is created, then a user who is waiting for a
|
|
64
64
|
// promise (i.e micro tick) for installing a 'error' listener will
|
|
65
65
|
// never get a chance and will always encounter an unhandled exception.
|
|
66
|
-
|
|
66
|
+
if (!this[kReading]) {
|
|
67
|
+
setImmediate(() => {
|
|
68
|
+
callback(err)
|
|
69
|
+
})
|
|
70
|
+
} else {
|
|
67
71
|
callback(err)
|
|
68
|
-
}
|
|
72
|
+
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
on (ev, ...args) {
|
|
@@ -524,6 +524,7 @@ function writeH2 (client, request) {
|
|
|
524
524
|
}
|
|
525
525
|
} else if (util.isStream(body)) {
|
|
526
526
|
writeStream({
|
|
527
|
+
abort,
|
|
527
528
|
body,
|
|
528
529
|
client,
|
|
529
530
|
request,
|
|
@@ -535,6 +536,7 @@ function writeH2 (client, request) {
|
|
|
535
536
|
})
|
|
536
537
|
} else if (util.isIterable(body)) {
|
|
537
538
|
writeIterable({
|
|
539
|
+
abort,
|
|
538
540
|
body,
|
|
539
541
|
client,
|
|
540
542
|
request,
|
|
@@ -7,13 +7,13 @@ const encoder = new TextEncoder()
|
|
|
7
7
|
/**
|
|
8
8
|
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point
|
|
9
9
|
*/
|
|
10
|
-
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'
|
|
10
|
+
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/
|
|
11
11
|
const HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/ // eslint-disable-line
|
|
12
12
|
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
|
|
13
13
|
/**
|
|
14
14
|
* @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point
|
|
15
15
|
*/
|
|
16
|
-
const HTTP_QUOTED_STRING_TOKENS =
|
|
16
|
+
const HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/ // eslint-disable-line
|
|
17
17
|
|
|
18
18
|
// https://fetch.spec.whatwg.org/#data-url-processor
|
|
19
19
|
/** @param {URL} dataURL */
|
package/lib/web/fetch/headers.js
CHANGED
|
@@ -250,7 +250,7 @@ class HeadersList {
|
|
|
250
250
|
get entries () {
|
|
251
251
|
const headers = {}
|
|
252
252
|
|
|
253
|
-
if (this[kHeadersMap].size) {
|
|
253
|
+
if (this[kHeadersMap].size !== 0) {
|
|
254
254
|
for (const { name, value } of this[kHeadersMap].values()) {
|
|
255
255
|
headers[name] = value
|
|
256
256
|
}
|
|
@@ -259,6 +259,10 @@ class HeadersList {
|
|
|
259
259
|
return headers
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
rawValues () {
|
|
263
|
+
return this[kHeadersMap].values()
|
|
264
|
+
}
|
|
265
|
+
|
|
262
266
|
get entriesList () {
|
|
263
267
|
const headers = []
|
|
264
268
|
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -120,12 +120,16 @@ class Fetch extends EE {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function handleFetchDone (response) {
|
|
124
|
+
finalizeAndReportTiming(response, 'fetch')
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
// https://fetch.spec.whatwg.org/#fetch-method
|
|
124
128
|
function fetch (input, init = undefined) {
|
|
125
129
|
webidl.argumentLengthCheck(arguments, 1, 'globalThis.fetch')
|
|
126
130
|
|
|
127
131
|
// 1. Let p be a new promise.
|
|
128
|
-
|
|
132
|
+
let p = createDeferredPromise()
|
|
129
133
|
|
|
130
134
|
// 2. Let requestObject be the result of invoking the initial value of
|
|
131
135
|
// Request as constructor with input and init as arguments. If this throws
|
|
@@ -185,16 +189,17 @@ function fetch (input, init = undefined) {
|
|
|
185
189
|
// 3. Abort controller with requestObject’s signal’s abort reason.
|
|
186
190
|
controller.abort(requestObject.signal.reason)
|
|
187
191
|
|
|
192
|
+
const realResponse = responseObject?.deref()
|
|
193
|
+
|
|
188
194
|
// 4. Abort the fetch() call with p, request, responseObject,
|
|
189
195
|
// and requestObject’s signal’s abort reason.
|
|
190
|
-
abortFetch(p, request,
|
|
196
|
+
abortFetch(p, request, realResponse, requestObject.signal.reason)
|
|
191
197
|
}
|
|
192
198
|
)
|
|
193
199
|
|
|
194
200
|
// 12. Let handleFetchDone given response response be to finalize and
|
|
195
201
|
// report timing with response, globalObject, and "fetch".
|
|
196
|
-
|
|
197
|
-
finalizeAndReportTiming(response, 'fetch')
|
|
202
|
+
// see function handleFetchDone
|
|
198
203
|
|
|
199
204
|
// 13. Set controller to the result of calling fetch given request,
|
|
200
205
|
// with processResponseEndOfBody set to handleFetchDone, and processResponse
|
|
@@ -228,10 +233,11 @@ function fetch (input, init = undefined) {
|
|
|
228
233
|
|
|
229
234
|
// 4. Set responseObject to the result of creating a Response object,
|
|
230
235
|
// given response, "immutable", and relevantRealm.
|
|
231
|
-
responseObject = fromInnerResponse(response, 'immutable')
|
|
236
|
+
responseObject = new WeakRef(fromInnerResponse(response, 'immutable'))
|
|
232
237
|
|
|
233
238
|
// 5. Resolve p with responseObject.
|
|
234
|
-
p.resolve(responseObject)
|
|
239
|
+
p.resolve(responseObject.deref())
|
|
240
|
+
p = null
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
controller = fetching({
|
|
@@ -314,7 +320,10 @@ const markResourceTiming = performance.markResourceTiming
|
|
|
314
320
|
// https://fetch.spec.whatwg.org/#abort-fetch
|
|
315
321
|
function abortFetch (p, request, responseObject, error) {
|
|
316
322
|
// 1. Reject promise with error.
|
|
317
|
-
p
|
|
323
|
+
if (p) {
|
|
324
|
+
// We might have already resolved the promise at this stage
|
|
325
|
+
p.reject(error)
|
|
326
|
+
}
|
|
318
327
|
|
|
319
328
|
// 2. If request’s body is not null and is readable, then cancel request’s
|
|
320
329
|
// body with error.
|
|
@@ -1066,7 +1075,10 @@ function fetchFinale (fetchParams, response) {
|
|
|
1066
1075
|
// 4. If fetchParams’s process response is non-null, then queue a fetch task to run fetchParams’s
|
|
1067
1076
|
// process response given response, with fetchParams’s task destination.
|
|
1068
1077
|
if (fetchParams.processResponse != null) {
|
|
1069
|
-
queueMicrotask(() =>
|
|
1078
|
+
queueMicrotask(() => {
|
|
1079
|
+
fetchParams.processResponse(response)
|
|
1080
|
+
fetchParams.processResponse = null
|
|
1081
|
+
})
|
|
1070
1082
|
}
|
|
1071
1083
|
|
|
1072
1084
|
// 5. Let internalResponse be response, if response is a network error; otherwise response’s internal response.
|
|
@@ -1884,7 +1896,11 @@ async function httpNetworkFetch (
|
|
|
1884
1896
|
// 12. Let cancelAlgorithm be an algorithm that aborts fetchParams’s
|
|
1885
1897
|
// controller with reason, given reason.
|
|
1886
1898
|
const cancelAlgorithm = (reason) => {
|
|
1887
|
-
|
|
1899
|
+
// If the aborted fetch was already terminated, then we do not
|
|
1900
|
+
// need to do anything.
|
|
1901
|
+
if (!isCancelled(fetchParams)) {
|
|
1902
|
+
fetchParams.controller.abort(reason)
|
|
1903
|
+
}
|
|
1888
1904
|
}
|
|
1889
1905
|
|
|
1890
1906
|
// 13. Let highWaterMark be a non-negative, non-NaN number, chosen by
|
|
@@ -2102,20 +2118,16 @@ async function httpNetworkFetch (
|
|
|
2102
2118
|
|
|
2103
2119
|
const headersList = new HeadersList()
|
|
2104
2120
|
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
2114
|
-
// "All content-coding values are case-insensitive..."
|
|
2115
|
-
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
|
|
2116
|
-
}
|
|
2117
|
-
location = headersList.get('location', true)
|
|
2121
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
2122
|
+
headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true)
|
|
2123
|
+
}
|
|
2124
|
+
const contentEncoding = headersList.get('content-encoding', true)
|
|
2125
|
+
if (contentEncoding) {
|
|
2126
|
+
// https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
|
|
2127
|
+
// "All content-coding values are case-insensitive..."
|
|
2128
|
+
codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim())
|
|
2118
2129
|
}
|
|
2130
|
+
location = headersList.get('location', true)
|
|
2119
2131
|
|
|
2120
2132
|
this.body = new Readable({ read: resume })
|
|
2121
2133
|
|
|
@@ -2125,7 +2137,7 @@ async function httpNetworkFetch (
|
|
|
2125
2137
|
redirectStatusSet.has(status)
|
|
2126
2138
|
|
|
2127
2139
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
|
2128
|
-
if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
|
2140
|
+
if (codings.length !== 0 && request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
|
|
2129
2141
|
for (let i = 0; i < codings.length; ++i) {
|
|
2130
2142
|
const coding = codings[i]
|
|
2131
2143
|
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
|
package/lib/web/fetch/request.js
CHANGED
|
@@ -477,9 +477,8 @@ class Request {
|
|
|
477
477
|
// 4. If headers is a Headers object, then for each header in its header
|
|
478
478
|
// list, append header’s name/header’s value to this’s headers.
|
|
479
479
|
if (headers instanceof HeadersList) {
|
|
480
|
-
for (const {
|
|
481
|
-
|
|
482
|
-
headersList.append(key, val, true)
|
|
480
|
+
for (const { name, value } of headers.rawValues()) {
|
|
481
|
+
headersList.append(name, value, false)
|
|
483
482
|
}
|
|
484
483
|
// Note: Copy the `set-cookie` meta-data.
|
|
485
484
|
headersList.cookies = headers.cookies
|
|
@@ -820,51 +819,50 @@ class Request {
|
|
|
820
819
|
|
|
821
820
|
mixinBody(Request)
|
|
822
821
|
|
|
822
|
+
// https://fetch.spec.whatwg.org/#requests
|
|
823
823
|
function makeRequest (init) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
824
|
+
return {
|
|
825
|
+
method: init.method ?? 'GET',
|
|
826
|
+
localURLsOnly: init.localURLsOnly ?? false,
|
|
827
|
+
unsafeRequest: init.unsafeRequest ?? false,
|
|
828
|
+
body: init.body ?? null,
|
|
829
|
+
client: init.client ?? null,
|
|
830
|
+
reservedClient: init.reservedClient ?? null,
|
|
831
|
+
replacesClientId: init.replacesClientId ?? '',
|
|
832
|
+
window: init.window ?? 'client',
|
|
833
|
+
keepalive: init.keepalive ?? false,
|
|
834
|
+
serviceWorkers: init.serviceWorkers ?? 'all',
|
|
835
|
+
initiator: init.initiator ?? '',
|
|
836
|
+
destination: init.destination ?? '',
|
|
837
|
+
priority: init.priority ?? null,
|
|
838
|
+
origin: init.origin ?? 'client',
|
|
839
|
+
policyContainer: init.policyContainer ?? 'client',
|
|
840
|
+
referrer: init.referrer ?? 'client',
|
|
841
|
+
referrerPolicy: init.referrerPolicy ?? '',
|
|
842
|
+
mode: init.mode ?? 'no-cors',
|
|
843
|
+
useCORSPreflightFlag: init.useCORSPreflightFlag ?? false,
|
|
844
|
+
credentials: init.credentials ?? 'same-origin',
|
|
845
|
+
useCredentials: init.useCredentials ?? false,
|
|
846
|
+
cache: init.cache ?? 'default',
|
|
847
|
+
redirect: init.redirect ?? 'follow',
|
|
848
|
+
integrity: init.integrity ?? '',
|
|
849
|
+
cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? '',
|
|
850
|
+
parserMetadata: init.parserMetadata ?? '',
|
|
851
|
+
reloadNavigation: init.reloadNavigation ?? false,
|
|
852
|
+
historyNavigation: init.historyNavigation ?? false,
|
|
853
|
+
userActivation: init.userActivation ?? false,
|
|
854
|
+
taintedOrigin: init.taintedOrigin ?? false,
|
|
855
|
+
redirectCount: init.redirectCount ?? 0,
|
|
856
|
+
responseTainting: init.responseTainting ?? 'basic',
|
|
857
|
+
preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false,
|
|
858
|
+
done: init.done ?? false,
|
|
859
|
+
timingAllowFailed: init.timingAllowFailed ?? false,
|
|
860
|
+
urlList: init.urlList,
|
|
861
|
+
url: init.urlList[0],
|
|
862
862
|
headersList: init.headersList
|
|
863
863
|
? new HeadersList(init.headersList)
|
|
864
864
|
: new HeadersList()
|
|
865
865
|
}
|
|
866
|
-
request.url = request.urlList[0]
|
|
867
|
-
return request
|
|
868
866
|
}
|
|
869
867
|
|
|
870
868
|
// https://fetch.spec.whatwg.org/#concept-request-clone
|
|
@@ -26,9 +26,23 @@ const { URLSerializer } = require('./data-url')
|
|
|
26
26
|
const { kHeadersList, kConstruct } = require('../../core/symbols')
|
|
27
27
|
const assert = require('node:assert')
|
|
28
28
|
const { types } = require('node:util')
|
|
29
|
+
const { isDisturbed, isErrored } = require('node:stream')
|
|
29
30
|
|
|
30
31
|
const textEncoder = new TextEncoder('utf-8')
|
|
31
32
|
|
|
33
|
+
const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
|
|
34
|
+
let registry
|
|
35
|
+
|
|
36
|
+
if (hasFinalizationRegistry) {
|
|
37
|
+
registry = new FinalizationRegistry((stream) => {
|
|
38
|
+
if (!stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
|
|
39
|
+
stream.cancel('Response object has been garbage collected').catch(noop)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function noop () {}
|
|
45
|
+
|
|
32
46
|
// https://fetch.spec.whatwg.org/#response-class
|
|
33
47
|
class Response {
|
|
34
48
|
// Creates network error Response.
|
|
@@ -510,6 +524,11 @@ function fromInnerResponse (innerResponse, guard) {
|
|
|
510
524
|
response[kHeaders] = new Headers(kConstruct)
|
|
511
525
|
response[kHeaders][kHeadersList] = innerResponse.headersList
|
|
512
526
|
response[kHeaders][kGuard] = guard
|
|
527
|
+
|
|
528
|
+
if (hasFinalizationRegistry && innerResponse.body?.stream) {
|
|
529
|
+
registry.register(response, innerResponse.body.stream)
|
|
530
|
+
}
|
|
531
|
+
|
|
513
532
|
return response
|
|
514
533
|
}
|
|
515
534
|
|
package/lib/web/fetch/webidl.js
CHANGED
|
@@ -250,6 +250,7 @@ webidl.sequenceConverter = function (converter) {
|
|
|
250
250
|
/** @type {Generator} */
|
|
251
251
|
const method = typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.()
|
|
252
252
|
const seq = []
|
|
253
|
+
let index = 0
|
|
253
254
|
|
|
254
255
|
// 3. If method is undefined, throw a TypeError.
|
|
255
256
|
if (
|
|
@@ -270,7 +271,7 @@ webidl.sequenceConverter = function (converter) {
|
|
|
270
271
|
break
|
|
271
272
|
}
|
|
272
273
|
|
|
273
|
-
seq.push(converter(value, prefix, argument))
|
|
274
|
+
seq.push(converter(value, prefix, `${argument}[${index++}]`))
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
return seq
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { uid, states, sentCloseFrameState } = require('./constants')
|
|
3
|
+
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
|
|
4
4
|
const {
|
|
5
5
|
kReadyState,
|
|
6
6
|
kSentClose,
|
|
7
7
|
kByteParser,
|
|
8
|
-
kReceivedClose
|
|
8
|
+
kReceivedClose,
|
|
9
|
+
kResponse
|
|
9
10
|
} = require('./symbols')
|
|
10
|
-
const { fireEvent, failWebsocketConnection } = require('./util')
|
|
11
|
+
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished } = require('./util')
|
|
11
12
|
const { channels } = require('../../core/diagnostics')
|
|
12
13
|
const { CloseEvent } = require('./events')
|
|
13
14
|
const { makeRequest } = require('../fetch/request')
|
|
@@ -15,6 +16,7 @@ const { fetching } = require('../fetch/index')
|
|
|
15
16
|
const { Headers } = require('../fetch/headers')
|
|
16
17
|
const { getDecodeSplit } = require('../fetch/util')
|
|
17
18
|
const { kHeadersList } = require('../../core/symbols')
|
|
19
|
+
const { WebsocketFrameSend } = require('./frame')
|
|
18
20
|
|
|
19
21
|
/** @type {import('crypto')} */
|
|
20
22
|
let crypto
|
|
@@ -211,6 +213,72 @@ function establishWebSocketConnection (url, protocols, ws, onEstablish, options)
|
|
|
211
213
|
return controller
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
function closeWebSocketConnection (ws, code, reason, reasonByteLength) {
|
|
217
|
+
if (isClosing(ws) || isClosed(ws)) {
|
|
218
|
+
// If this's ready state is CLOSING (2) or CLOSED (3)
|
|
219
|
+
// Do nothing.
|
|
220
|
+
} else if (!isEstablished(ws)) {
|
|
221
|
+
// If the WebSocket connection is not yet established
|
|
222
|
+
// Fail the WebSocket connection and set this's ready state
|
|
223
|
+
// to CLOSING (2).
|
|
224
|
+
failWebsocketConnection(ws, 'Connection was closed before it was established.')
|
|
225
|
+
ws[kReadyState] = states.CLOSING
|
|
226
|
+
} else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) {
|
|
227
|
+
// If the WebSocket closing handshake has not yet been started
|
|
228
|
+
// Start the WebSocket closing handshake and set this's ready
|
|
229
|
+
// state to CLOSING (2).
|
|
230
|
+
// - If neither code nor reason is present, the WebSocket Close
|
|
231
|
+
// message must not have a body.
|
|
232
|
+
// - If code is present, then the status code to use in the
|
|
233
|
+
// WebSocket Close message must be the integer given by code.
|
|
234
|
+
// - If reason is also present, then reasonBytes must be
|
|
235
|
+
// provided in the Close message after the status code.
|
|
236
|
+
|
|
237
|
+
ws[kSentClose] = sentCloseFrameState.PROCESSING
|
|
238
|
+
|
|
239
|
+
const frame = new WebsocketFrameSend()
|
|
240
|
+
|
|
241
|
+
// If neither code nor reason is present, the WebSocket Close
|
|
242
|
+
// message must not have a body.
|
|
243
|
+
|
|
244
|
+
// If code is present, then the status code to use in the
|
|
245
|
+
// WebSocket Close message must be the integer given by code.
|
|
246
|
+
if (code !== undefined && reason === undefined) {
|
|
247
|
+
frame.frameData = Buffer.allocUnsafe(2)
|
|
248
|
+
frame.frameData.writeUInt16BE(code, 0)
|
|
249
|
+
} else if (code !== undefined && reason !== undefined) {
|
|
250
|
+
// If reason is also present, then reasonBytes must be
|
|
251
|
+
// provided in the Close message after the status code.
|
|
252
|
+
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
|
|
253
|
+
frame.frameData.writeUInt16BE(code, 0)
|
|
254
|
+
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
255
|
+
frame.frameData.write(reason, 2, 'utf-8')
|
|
256
|
+
} else {
|
|
257
|
+
frame.frameData = emptyBuffer
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** @type {import('stream').Duplex} */
|
|
261
|
+
const socket = ws[kResponse].socket
|
|
262
|
+
|
|
263
|
+
socket.write(frame.createFrame(opcodes.CLOSE), (err) => {
|
|
264
|
+
if (!err) {
|
|
265
|
+
ws[kSentClose] = sentCloseFrameState.SENT
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
ws[kSentClose] = sentCloseFrameState.PROCESSING
|
|
270
|
+
|
|
271
|
+
// Upon either sending or receiving a Close control frame, it is said
|
|
272
|
+
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
273
|
+
// WebSocket connection is in the CLOSING state.
|
|
274
|
+
ws[kReadyState] = states.CLOSING
|
|
275
|
+
} else {
|
|
276
|
+
// Otherwise
|
|
277
|
+
// Set this's ready state to CLOSING (2).
|
|
278
|
+
ws[kReadyState] = states.CLOSING
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
214
282
|
/**
|
|
215
283
|
* @param {Buffer} chunk
|
|
216
284
|
*/
|
|
@@ -237,10 +305,10 @@ function onSocketClose () {
|
|
|
237
305
|
|
|
238
306
|
const result = ws[kByteParser].closingInfo
|
|
239
307
|
|
|
240
|
-
if (result) {
|
|
308
|
+
if (result && !result.error) {
|
|
241
309
|
code = result.code ?? 1005
|
|
242
310
|
reason = result.reason
|
|
243
|
-
} else if (ws[
|
|
311
|
+
} else if (!ws[kReceivedClose]) {
|
|
244
312
|
// If _The WebSocket
|
|
245
313
|
// Connection is Closed_ and no Close control frame was received by the
|
|
246
314
|
// endpoint (such as could occur if the underlying transport connection
|
|
@@ -293,5 +361,6 @@ function onSocketError (error) {
|
|
|
293
361
|
}
|
|
294
362
|
|
|
295
363
|
module.exports = {
|
|
296
|
-
establishWebSocketConnection
|
|
364
|
+
establishWebSocketConnection,
|
|
365
|
+
closeWebSocketConnection
|
|
297
366
|
}
|
|
@@ -2,13 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
const { maxUnsigned16Bit } = require('./constants')
|
|
4
4
|
|
|
5
|
+
const BUFFER_SIZE = 16386
|
|
6
|
+
|
|
5
7
|
/** @type {import('crypto')} */
|
|
6
8
|
let crypto
|
|
9
|
+
let buffer = null
|
|
10
|
+
let bufIdx = BUFFER_SIZE
|
|
11
|
+
|
|
7
12
|
try {
|
|
8
13
|
crypto = require('node:crypto')
|
|
9
14
|
/* c8 ignore next 3 */
|
|
10
15
|
} catch {
|
|
16
|
+
crypto = {
|
|
17
|
+
// not full compatibility, but minimum.
|
|
18
|
+
randomFillSync: function randomFillSync (buffer, _offset, _size) {
|
|
19
|
+
for (let i = 0; i < buffer.length; ++i) {
|
|
20
|
+
buffer[i] = Math.random() * 255 | 0
|
|
21
|
+
}
|
|
22
|
+
return buffer
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
11
26
|
|
|
27
|
+
function generateMask () {
|
|
28
|
+
if (bufIdx === BUFFER_SIZE) {
|
|
29
|
+
bufIdx = 0
|
|
30
|
+
crypto.randomFillSync((buffer ??= Buffer.allocUnsafe(BUFFER_SIZE)), 0, BUFFER_SIZE)
|
|
31
|
+
}
|
|
32
|
+
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
|
|
12
33
|
}
|
|
13
34
|
|
|
14
35
|
class WebsocketFrameSend {
|
|
@@ -17,11 +38,12 @@ class WebsocketFrameSend {
|
|
|
17
38
|
*/
|
|
18
39
|
constructor (data) {
|
|
19
40
|
this.frameData = data
|
|
20
|
-
this.maskKey = crypto.randomBytes(4)
|
|
21
41
|
}
|
|
22
42
|
|
|
23
43
|
createFrame (opcode) {
|
|
24
|
-
const
|
|
44
|
+
const frameData = this.frameData
|
|
45
|
+
const maskKey = generateMask()
|
|
46
|
+
const bodyLength = frameData?.byteLength ?? 0
|
|
25
47
|
|
|
26
48
|
/** @type {number} */
|
|
27
49
|
let payloadLength = bodyLength // 0-125
|
|
@@ -43,10 +65,10 @@ class WebsocketFrameSend {
|
|
|
43
65
|
buffer[0] = (buffer[0] & 0xF0) + opcode // opcode
|
|
44
66
|
|
|
45
67
|
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
|
|
46
|
-
buffer[offset - 4] =
|
|
47
|
-
buffer[offset - 3] =
|
|
48
|
-
buffer[offset - 2] =
|
|
49
|
-
buffer[offset - 1] =
|
|
68
|
+
buffer[offset - 4] = maskKey[0]
|
|
69
|
+
buffer[offset - 3] = maskKey[1]
|
|
70
|
+
buffer[offset - 2] = maskKey[2]
|
|
71
|
+
buffer[offset - 1] = maskKey[3]
|
|
50
72
|
|
|
51
73
|
buffer[1] = payloadLength
|
|
52
74
|
|
|
@@ -61,8 +83,8 @@ class WebsocketFrameSend {
|
|
|
61
83
|
buffer[1] |= 0x80 // MASK
|
|
62
84
|
|
|
63
85
|
// mask body
|
|
64
|
-
for (let i = 0; i < bodyLength; i
|
|
65
|
-
buffer[offset + i] =
|
|
86
|
+
for (let i = 0; i < bodyLength; ++i) {
|
|
87
|
+
buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
return buffer
|
|
@@ -6,6 +6,7 @@ const { kReadyState, kSentClose, kResponse, kReceivedClose } = require('./symbol
|
|
|
6
6
|
const { channels } = require('../../core/diagnostics')
|
|
7
7
|
const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived, utf8Decode } = require('./util')
|
|
8
8
|
const { WebsocketFrameSend } = require('./frame')
|
|
9
|
+
const { CloseEvent } = require('./events')
|
|
9
10
|
|
|
10
11
|
// This code was influenced by ws released under the MIT license.
|
|
11
12
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
@@ -55,6 +56,12 @@ class ByteParser extends Writable {
|
|
|
55
56
|
|
|
56
57
|
this.#info.fin = (buffer[0] & 0x80) !== 0
|
|
57
58
|
this.#info.opcode = buffer[0] & 0x0F
|
|
59
|
+
this.#info.masked = (buffer[1] & 0x80) === 0x80
|
|
60
|
+
|
|
61
|
+
if (this.#info.masked) {
|
|
62
|
+
failWebsocketConnection(this.ws, 'Frame cannot be masked')
|
|
63
|
+
return callback()
|
|
64
|
+
}
|
|
58
65
|
|
|
59
66
|
// If we receive a fragmented message, we use the type of the first
|
|
60
67
|
// frame to parse the full message as binary/text, when it's terminated
|
|
@@ -102,6 +109,13 @@ class ByteParser extends Writable {
|
|
|
102
109
|
|
|
103
110
|
this.#info.closeInfo = this.parseCloseBody(body)
|
|
104
111
|
|
|
112
|
+
if (this.#info.closeInfo.error) {
|
|
113
|
+
const { code, reason } = this.#info.closeInfo
|
|
114
|
+
|
|
115
|
+
callback(new CloseEvent('close', { wasClean: false, reason, code }))
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
if (this.ws[kSentClose] !== sentCloseFrameState.SENT) {
|
|
106
120
|
// If an endpoint receives a Close frame and did not previously send a
|
|
107
121
|
// Close frame, the endpoint MUST send a Close frame in response. (When
|
|
@@ -239,7 +253,7 @@ class ByteParser extends Writable {
|
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
|
|
242
|
-
if (this.#byteOffset === 0) {
|
|
256
|
+
if (this.#byteOffset === 0 && this.#info.payloadLength !== 0) {
|
|
243
257
|
callback()
|
|
244
258
|
break
|
|
245
259
|
}
|
|
@@ -310,16 +324,16 @@ class ByteParser extends Writable {
|
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
if (code !== undefined && !isValidStatusCode(code)) {
|
|
313
|
-
return
|
|
327
|
+
return { code: 1002, reason: 'Invalid status code', error: true }
|
|
314
328
|
}
|
|
315
329
|
|
|
316
330
|
try {
|
|
317
331
|
reason = utf8Decode(reason)
|
|
318
332
|
} catch {
|
|
319
|
-
return
|
|
333
|
+
return { code: 1007, reason: 'Invalid UTF-8', error: true }
|
|
320
334
|
}
|
|
321
335
|
|
|
322
|
-
return { code, reason }
|
|
336
|
+
return { code, reason, error: false }
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
get closingInfo () {
|
|
@@ -104,7 +104,7 @@ function websocketMessageReceived (ws, type, data) {
|
|
|
104
104
|
// -> type indicates that the data is Binary and binary type is "arraybuffer"
|
|
105
105
|
// a new ArrayBuffer object, created in the relevant Realm of the
|
|
106
106
|
// WebSocket object, whose contents are data
|
|
107
|
-
dataForEvent =
|
|
107
|
+
dataForEvent = toArrayBuffer(data)
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -117,6 +117,13 @@ function websocketMessageReceived (ws, type, data) {
|
|
|
117
117
|
})
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
function toArrayBuffer (buffer) {
|
|
121
|
+
if (buffer.byteLength === buffer.buffer.byteLength) {
|
|
122
|
+
return buffer.buffer
|
|
123
|
+
}
|
|
124
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
125
|
+
}
|
|
126
|
+
|
|
120
127
|
/**
|
|
121
128
|
* @see https://datatracker.ietf.org/doc/html/rfc6455
|
|
122
129
|
* @see https://datatracker.ietf.org/doc/html/rfc2616
|
|
@@ -197,7 +204,8 @@ function failWebsocketConnection (ws, reason) {
|
|
|
197
204
|
if (reason) {
|
|
198
205
|
// TODO: process.nextTick
|
|
199
206
|
fireEvent('error', ws, (type, init) => new ErrorEvent(type, init), {
|
|
200
|
-
error: new Error(reason)
|
|
207
|
+
error: new Error(reason),
|
|
208
|
+
message: reason
|
|
201
209
|
})
|
|
202
210
|
}
|
|
203
211
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { webidl } = require('../fetch/webidl')
|
|
4
4
|
const { URLSerializer } = require('../fetch/data-url')
|
|
5
|
-
const {
|
|
6
|
-
const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes
|
|
5
|
+
const { environmentSettingsObject } = require('../fetch/util')
|
|
6
|
+
const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes } = require('./constants')
|
|
7
7
|
const {
|
|
8
8
|
kWebSocketURL,
|
|
9
9
|
kReadyState,
|
|
@@ -16,21 +16,22 @@ const {
|
|
|
16
16
|
const {
|
|
17
17
|
isConnecting,
|
|
18
18
|
isEstablished,
|
|
19
|
-
isClosed,
|
|
20
19
|
isClosing,
|
|
21
20
|
isValidSubprotocol,
|
|
22
|
-
failWebsocketConnection,
|
|
23
21
|
fireEvent
|
|
24
22
|
} = require('./util')
|
|
25
|
-
const { establishWebSocketConnection } = require('./connection')
|
|
23
|
+
const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
|
|
26
24
|
const { WebsocketFrameSend } = require('./frame')
|
|
27
25
|
const { ByteParser } = require('./receiver')
|
|
28
26
|
const { kEnumerableProperty, isBlobLike } = require('../../core/util')
|
|
29
27
|
const { getGlobalDispatcher } = require('../../global')
|
|
30
28
|
const { types } = require('node:util')
|
|
29
|
+
const { ErrorEvent } = require('./events')
|
|
31
30
|
|
|
32
31
|
let experimentalWarned = false
|
|
33
32
|
|
|
33
|
+
const FastBuffer = Buffer[Symbol.species]
|
|
34
|
+
|
|
34
35
|
// https://websockets.spec.whatwg.org/#interface-definition
|
|
35
36
|
class WebSocket extends EventTarget {
|
|
36
37
|
#events = {
|
|
@@ -67,7 +68,7 @@ class WebSocket extends EventTarget {
|
|
|
67
68
|
protocols = options.protocols
|
|
68
69
|
|
|
69
70
|
// 1. Let baseURL be this's relevant settings object's API base URL.
|
|
70
|
-
const baseURL =
|
|
71
|
+
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
|
71
72
|
|
|
72
73
|
// 1. Let urlRecord be the result of applying the URL parser to url with baseURL.
|
|
73
74
|
let urlRecord
|
|
@@ -197,67 +198,7 @@ class WebSocket extends EventTarget {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
// 3. Run the first matching steps from the following list:
|
|
200
|
-
|
|
201
|
-
// If this's ready state is CLOSING (2) or CLOSED (3)
|
|
202
|
-
// Do nothing.
|
|
203
|
-
} else if (!isEstablished(this)) {
|
|
204
|
-
// If the WebSocket connection is not yet established
|
|
205
|
-
// Fail the WebSocket connection and set this's ready state
|
|
206
|
-
// to CLOSING (2).
|
|
207
|
-
failWebsocketConnection(this, 'Connection was closed before it was established.')
|
|
208
|
-
this[kReadyState] = WebSocket.CLOSING
|
|
209
|
-
} else if (this[kSentClose] === sentCloseFrameState.NOT_SENT) {
|
|
210
|
-
// If the WebSocket closing handshake has not yet been started
|
|
211
|
-
// Start the WebSocket closing handshake and set this's ready
|
|
212
|
-
// state to CLOSING (2).
|
|
213
|
-
// - If neither code nor reason is present, the WebSocket Close
|
|
214
|
-
// message must not have a body.
|
|
215
|
-
// - If code is present, then the status code to use in the
|
|
216
|
-
// WebSocket Close message must be the integer given by code.
|
|
217
|
-
// - If reason is also present, then reasonBytes must be
|
|
218
|
-
// provided in the Close message after the status code.
|
|
219
|
-
|
|
220
|
-
this[kSentClose] = sentCloseFrameState.PROCESSING
|
|
221
|
-
|
|
222
|
-
const frame = new WebsocketFrameSend()
|
|
223
|
-
|
|
224
|
-
// If neither code nor reason is present, the WebSocket Close
|
|
225
|
-
// message must not have a body.
|
|
226
|
-
|
|
227
|
-
// If code is present, then the status code to use in the
|
|
228
|
-
// WebSocket Close message must be the integer given by code.
|
|
229
|
-
if (code !== undefined && reason === undefined) {
|
|
230
|
-
frame.frameData = Buffer.allocUnsafe(2)
|
|
231
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
232
|
-
} else if (code !== undefined && reason !== undefined) {
|
|
233
|
-
// If reason is also present, then reasonBytes must be
|
|
234
|
-
// provided in the Close message after the status code.
|
|
235
|
-
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
|
|
236
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
237
|
-
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
238
|
-
frame.frameData.write(reason, 2, 'utf-8')
|
|
239
|
-
} else {
|
|
240
|
-
frame.frameData = emptyBuffer
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** @type {import('stream').Duplex} */
|
|
244
|
-
const socket = this[kResponse].socket
|
|
245
|
-
|
|
246
|
-
socket.write(frame.createFrame(opcodes.CLOSE), (err) => {
|
|
247
|
-
if (!err) {
|
|
248
|
-
this[kSentClose] = sentCloseFrameState.SENT
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
// Upon either sending or receiving a Close control frame, it is said
|
|
253
|
-
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
254
|
-
// WebSocket connection is in the CLOSING state.
|
|
255
|
-
this[kReadyState] = states.CLOSING
|
|
256
|
-
} else {
|
|
257
|
-
// Otherwise
|
|
258
|
-
// Set this's ready state to CLOSING (2).
|
|
259
|
-
this[kReadyState] = WebSocket.CLOSING
|
|
260
|
-
}
|
|
201
|
+
closeWebSocketConnection(this, code, reason, reasonByteLength)
|
|
261
202
|
}
|
|
262
203
|
|
|
263
204
|
/**
|
|
@@ -323,7 +264,7 @@ class WebSocket extends EventTarget {
|
|
|
323
264
|
// increase the bufferedAmount attribute by the length of the
|
|
324
265
|
// ArrayBuffer in bytes.
|
|
325
266
|
|
|
326
|
-
const value =
|
|
267
|
+
const value = new FastBuffer(data)
|
|
327
268
|
const frame = new WebsocketFrameSend(value)
|
|
328
269
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|
329
270
|
|
|
@@ -344,7 +285,7 @@ class WebSocket extends EventTarget {
|
|
|
344
285
|
// not throw an exception must increase the bufferedAmount attribute
|
|
345
286
|
// by the length of data’s buffer in bytes.
|
|
346
287
|
|
|
347
|
-
const ab =
|
|
288
|
+
const ab = new FastBuffer(data, data.byteOffset, data.byteLength)
|
|
348
289
|
|
|
349
290
|
const frame = new WebsocketFrameSend(ab)
|
|
350
291
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|
@@ -368,7 +309,7 @@ class WebSocket extends EventTarget {
|
|
|
368
309
|
const frame = new WebsocketFrameSend()
|
|
369
310
|
|
|
370
311
|
data.arrayBuffer().then((ab) => {
|
|
371
|
-
const value =
|
|
312
|
+
const value = new FastBuffer(ab)
|
|
372
313
|
frame.frameData = value
|
|
373
314
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|
374
315
|
|
|
@@ -521,9 +462,8 @@ class WebSocket extends EventTarget {
|
|
|
521
462
|
this[kResponse] = response
|
|
522
463
|
|
|
523
464
|
const parser = new ByteParser(this)
|
|
524
|
-
parser.on('drain',
|
|
525
|
-
|
|
526
|
-
})
|
|
465
|
+
parser.on('drain', onParserDrain)
|
|
466
|
+
parser.on('error', onParserError.bind(this))
|
|
527
467
|
|
|
528
468
|
response.socket.ws = this
|
|
529
469
|
this[kByteParser] = parser
|
|
@@ -647,6 +587,16 @@ webidl.converters.WebSocketSendData = function (V) {
|
|
|
647
587
|
return webidl.converters.USVString(V)
|
|
648
588
|
}
|
|
649
589
|
|
|
590
|
+
function onParserDrain () {
|
|
591
|
+
this.ws[kResponse].socket.resume()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function onParserError (err) {
|
|
595
|
+
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message: err.reason }))
|
|
596
|
+
|
|
597
|
+
closeWebSocketConnection(this, err.code)
|
|
598
|
+
}
|
|
599
|
+
|
|
650
600
|
module.exports = {
|
|
651
601
|
WebSocket
|
|
652
602
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.16.0",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"test:eventsource:nobuild": "borp --expose-gc -p \"test/eventsource/*.js\"",
|
|
78
78
|
"test:fuzzing": "node test/fuzzing/fuzzing.test.js",
|
|
79
79
|
"test:fetch": "npm run build:node && npm run test:fetch:nobuild",
|
|
80
|
-
"test:fetch:nobuild": "borp --expose-gc -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
|
|
80
|
+
"test:fetch:nobuild": "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\"",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@sinonjs/fake-timers": "^11.1.0",
|
|
106
106
|
"@types/node": "^18.0.3",
|
|
107
107
|
"abort-controller": "^3.0.0",
|
|
108
|
-
"borp": "^0.
|
|
108
|
+
"borp": "^0.13.0",
|
|
109
109
|
"c8": "^9.1.0",
|
|
110
110
|
"cross-env": "^7.0.3",
|
|
111
111
|
"dns-packet": "^5.4.0",
|
package/types/fetch.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ export declare class Headers implements SpecIterable<[string, string]> {
|
|
|
85
85
|
readonly keys: () => SpecIterableIterator<string>
|
|
86
86
|
readonly values: () => SpecIterableIterator<string>
|
|
87
87
|
readonly entries: () => SpecIterableIterator<[string, string]>
|
|
88
|
-
readonly [Symbol.iterator]: () =>
|
|
88
|
+
readonly [Symbol.iterator]: () => SpecIterableIterator<[string, string]>
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export type RequestCache =
|
|
@@ -163,6 +163,7 @@ export declare class Request extends BodyMixin {
|
|
|
163
163
|
readonly method: string
|
|
164
164
|
readonly mode: RequestMode
|
|
165
165
|
readonly redirect: RequestRedirect
|
|
166
|
+
readonly referrer: string
|
|
166
167
|
readonly referrerPolicy: ReferrerPolicy
|
|
167
168
|
readonly url: string
|
|
168
169
|
|
|
@@ -71,11 +71,11 @@ declare namespace MockInterceptor {
|
|
|
71
71
|
|
|
72
72
|
export interface MockResponseCallbackOptions {
|
|
73
73
|
path: string;
|
|
74
|
-
origin: string;
|
|
75
74
|
method: string;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
headers?: Headers | Record<string, string>;
|
|
76
|
+
origin?: string;
|
|
77
|
+
body?: BodyInit | Dispatcher.DispatchOptions['body'] | null;
|
|
78
|
+
maxRedirections?: number;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export type MockResponseDataHandler<TData extends object = object> = (
|
package/types/util.d.ts
CHANGED
|
@@ -8,24 +8,11 @@ export namespace util {
|
|
|
8
8
|
/**
|
|
9
9
|
* Receives a header object and returns the parsed value.
|
|
10
10
|
* @param headers Header object
|
|
11
|
+
* @param obj Object to specify a proxy object. Used to assign parsed values.
|
|
12
|
+
* @returns If `obj` is specified, it is equivalent to `obj`.
|
|
11
13
|
*/
|
|
12
14
|
export function parseHeaders(
|
|
13
|
-
headers:
|
|
14
|
-
|
|
15
|
-
| (Buffer | string | (Buffer | string)[])[]
|
|
16
|
-
): Record<string, string | string[]>;
|
|
17
|
-
/**
|
|
18
|
-
* Receives a header object and returns the parsed value.
|
|
19
|
-
* @param headers Header object
|
|
20
|
-
* @param obj Object to specify a proxy object. Used to assign parsed values. But, if `headers` is an object, it is not used.
|
|
21
|
-
* @returns If `headers` is an object, it is `headers`. Otherwise, if `obj` is specified, it is equivalent to `obj`.
|
|
22
|
-
*/
|
|
23
|
-
export function parseHeaders<
|
|
24
|
-
H extends
|
|
25
|
-
| Record<string, string | string[]>
|
|
26
|
-
| (Buffer | string | (Buffer | string)[])[]
|
|
27
|
-
>(
|
|
28
|
-
headers: H,
|
|
29
|
-
obj?: H extends any[] ? Record<string, string | string[]> : never
|
|
15
|
+
headers: (Buffer | string | (Buffer | string)[])[],
|
|
16
|
+
obj?: Record<string, string | string[]>
|
|
30
17
|
): Record<string, string | string[]>;
|
|
31
18
|
}
|