undici 5.20.0 → 5.21.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/README.md +1 -1
- package/docs/api/ProxyAgent.md +3 -1
- package/lib/api/api-stream.js +33 -5
- package/lib/api/readable.js +18 -2
- package/lib/client.js +14 -0
- package/lib/core/util.js +33 -15
- package/lib/fetch/dataURL.js +11 -2
- package/lib/fetch/index.js +1 -1
- package/lib/fetch/request.js +16 -8
- package/lib/fetch/util.js +98 -93
- package/lib/fileapi/encoding.js +5 -1
- package/lib/proxy-agent.js +12 -2
- package/lib/timers.js +21 -13
- package/lib/websocket/connection.js +12 -61
- package/lib/websocket/symbols.js +0 -3
- package/lib/websocket/websocket.js +55 -9
- package/package.json +3 -3
- package/types/balanced-pool.d.ts +3 -3
- package/types/client.d.ts +49 -27
- package/types/proxy-agent.d.ts +3 -0
- package/types/websocket.d.ts +1 -0
package/README.md
CHANGED
|
@@ -407,7 +407,7 @@ Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
|
|
|
407
407
|
|
|
408
408
|
## Workarounds
|
|
409
409
|
|
|
410
|
-
###
|
|
410
|
+
### Network address family autoselection.
|
|
411
411
|
|
|
412
412
|
If you experience problem when connecting to a remote server that is resolved by your DNS servers to a IPv6 (AAAA record)
|
|
413
413
|
first, there are chances that your local router or ISP might have problem connecting to IPv6 networks. In that case
|
package/docs/api/ProxyAgent.md
CHANGED
|
@@ -19,6 +19,7 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
|
|
|
19
19
|
* **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
|
|
20
20
|
* **token** `string` (optional) - It can be passed by a string of token for authentication.
|
|
21
21
|
* **auth** `string` (**deprecated**) - Use token.
|
|
22
|
+
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
22
23
|
|
|
23
24
|
Examples:
|
|
24
25
|
|
|
@@ -83,7 +84,8 @@ import { setGlobalDispatcher, request, ProxyAgent } from 'undici';
|
|
|
83
84
|
|
|
84
85
|
const proxyAgent = new ProxyAgent({
|
|
85
86
|
uri: 'my.proxy.server',
|
|
86
|
-
token: 'Bearer xxxx'
|
|
87
|
+
// token: 'Bearer xxxx'
|
|
88
|
+
token: `Basic ${Buffer.from('username:password').toString('base64')}`
|
|
87
89
|
});
|
|
88
90
|
setGlobalDispatcher(proxyAgent);
|
|
89
91
|
|
package/lib/api/api-stream.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { finished } = require('stream')
|
|
3
|
+
const { finished, PassThrough } = require('stream')
|
|
4
4
|
const {
|
|
5
5
|
InvalidArgumentError,
|
|
6
6
|
InvalidReturnValueError,
|
|
7
|
-
RequestAbortedError
|
|
7
|
+
RequestAbortedError,
|
|
8
|
+
ResponseStatusCodeError
|
|
8
9
|
} = require('../core/errors')
|
|
9
10
|
const util = require('../core/util')
|
|
10
11
|
const { AsyncResource } = require('async_hooks')
|
|
@@ -16,7 +17,7 @@ class StreamHandler extends AsyncResource {
|
|
|
16
17
|
throw new InvalidArgumentError('invalid opts')
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
20
|
+
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
23
|
if (typeof callback !== 'function') {
|
|
@@ -57,6 +58,7 @@ class StreamHandler extends AsyncResource {
|
|
|
57
58
|
this.trailers = null
|
|
58
59
|
this.body = body
|
|
59
60
|
this.onInfo = onInfo || null
|
|
61
|
+
this.throwOnError = throwOnError || false
|
|
60
62
|
|
|
61
63
|
if (util.isStream(body)) {
|
|
62
64
|
body.on('error', (err) => {
|
|
@@ -76,8 +78,8 @@ class StreamHandler extends AsyncResource {
|
|
|
76
78
|
this.context = context
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
onHeaders (statusCode, rawHeaders, resume) {
|
|
80
|
-
const { factory, opaque, context } = this
|
|
81
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
82
|
+
const { factory, opaque, context, callback } = this
|
|
81
83
|
|
|
82
84
|
if (statusCode < 200) {
|
|
83
85
|
if (this.onInfo) {
|
|
@@ -96,6 +98,32 @@ class StreamHandler extends AsyncResource {
|
|
|
96
98
|
context
|
|
97
99
|
})
|
|
98
100
|
|
|
101
|
+
if (this.throwOnError && statusCode >= 400) {
|
|
102
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
103
|
+
const chunks = []
|
|
104
|
+
const pt = new PassThrough()
|
|
105
|
+
pt
|
|
106
|
+
.on('data', (chunk) => chunks.push(chunk))
|
|
107
|
+
.on('end', () => {
|
|
108
|
+
const payload = Buffer.concat(chunks).toString('utf8')
|
|
109
|
+
this.runInAsyncScope(
|
|
110
|
+
callback,
|
|
111
|
+
null,
|
|
112
|
+
new ResponseStatusCodeError(
|
|
113
|
+
`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`,
|
|
114
|
+
statusCode,
|
|
115
|
+
headers,
|
|
116
|
+
payload
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
.on('error', (err) => {
|
|
121
|
+
this.onError(err)
|
|
122
|
+
})
|
|
123
|
+
this.res = pt
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
99
127
|
if (
|
|
100
128
|
!res ||
|
|
101
129
|
typeof res.write !== 'function' ||
|
package/lib/api/readable.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const assert = require('assert')
|
|
6
6
|
const { Readable } = require('stream')
|
|
7
|
-
const { RequestAbortedError, NotSupportedError } = require('../core/errors')
|
|
7
|
+
const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = require('../core/errors')
|
|
8
8
|
const util = require('../core/util')
|
|
9
9
|
const { ReadableStreamFrom, toUSVString } = require('../core/util')
|
|
10
10
|
|
|
@@ -146,15 +146,31 @@ module.exports = class BodyReadable extends Readable {
|
|
|
146
146
|
|
|
147
147
|
async dump (opts) {
|
|
148
148
|
let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144
|
|
149
|
+
const signal = opts && opts.signal
|
|
150
|
+
const abortFn = () => {
|
|
151
|
+
this.destroy()
|
|
152
|
+
}
|
|
153
|
+
if (signal) {
|
|
154
|
+
if (typeof signal !== 'object' || !('aborted' in signal)) {
|
|
155
|
+
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
156
|
+
}
|
|
157
|
+
util.throwIfAborted(signal)
|
|
158
|
+
signal.addEventListener('abort', abortFn, { once: true })
|
|
159
|
+
}
|
|
149
160
|
try {
|
|
150
161
|
for await (const chunk of this) {
|
|
162
|
+
util.throwIfAborted(signal)
|
|
151
163
|
limit -= Buffer.byteLength(chunk)
|
|
152
164
|
if (limit < 0) {
|
|
153
165
|
return
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
} catch {
|
|
157
|
-
|
|
169
|
+
util.throwIfAborted(signal)
|
|
170
|
+
} finally {
|
|
171
|
+
if (signal) {
|
|
172
|
+
signal.removeEventListener('abort', abortFn)
|
|
173
|
+
}
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
176
|
}
|
package/lib/client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict'
|
|
2
4
|
|
|
3
5
|
/* global WebAssembly */
|
|
@@ -85,7 +87,15 @@ try {
|
|
|
85
87
|
channels.connected = { hasSubscribers: false }
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @type {import('../types/client').default}
|
|
92
|
+
*/
|
|
88
93
|
class Client extends DispatcherBase {
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {string|URL} url
|
|
97
|
+
* @param {import('../types/client').Client.Options} options
|
|
98
|
+
*/
|
|
89
99
|
constructor (url, {
|
|
90
100
|
interceptors,
|
|
91
101
|
maxHeaderSize,
|
|
@@ -1658,6 +1668,8 @@ class AsyncWriter {
|
|
|
1658
1668
|
process.emitWarning(new RequestContentLengthMismatchError())
|
|
1659
1669
|
}
|
|
1660
1670
|
|
|
1671
|
+
socket.cork()
|
|
1672
|
+
|
|
1661
1673
|
if (bytesWritten === 0) {
|
|
1662
1674
|
if (!expectsPayload) {
|
|
1663
1675
|
socket[kReset] = true
|
|
@@ -1678,6 +1690,8 @@ class AsyncWriter {
|
|
|
1678
1690
|
|
|
1679
1691
|
const ret = socket.write(chunk)
|
|
1680
1692
|
|
|
1693
|
+
socket.uncork()
|
|
1694
|
+
|
|
1681
1695
|
request.onBodySent(chunk)
|
|
1682
1696
|
|
|
1683
1697
|
if (!ret) {
|
package/lib/core/util.js
CHANGED
|
@@ -15,7 +15,7 @@ const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(
|
|
|
15
15
|
function nop () {}
|
|
16
16
|
|
|
17
17
|
function isStream (obj) {
|
|
18
|
-
return obj && typeof obj.pipe === 'function'
|
|
18
|
+
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
|
@@ -46,6 +46,12 @@ function buildURL (url, queryParams) {
|
|
|
46
46
|
function parseURL (url) {
|
|
47
47
|
if (typeof url === 'string') {
|
|
48
48
|
url = new URL(url)
|
|
49
|
+
|
|
50
|
+
if (!/^https?:/.test(url.origin || url.protocol)) {
|
|
51
|
+
throw new InvalidArgumentError('invalid protocol')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return url
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
if (!url || typeof url !== 'object') {
|
|
@@ -375,23 +381,34 @@ function ReadableStreamFrom (iterable) {
|
|
|
375
381
|
|
|
376
382
|
// The chunk should be a FormData instance and contains
|
|
377
383
|
// all the required methods.
|
|
378
|
-
function isFormDataLike (
|
|
379
|
-
return (
|
|
380
|
-
|
|
381
|
-
typeof
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
typeof chunk.keys === 'function' &&
|
|
390
|
-
typeof chunk.values === 'function' &&
|
|
391
|
-
typeof chunk.forEach === 'function')
|
|
384
|
+
function isFormDataLike (object) {
|
|
385
|
+
return (
|
|
386
|
+
object &&
|
|
387
|
+
typeof object === 'object' &&
|
|
388
|
+
typeof object.append === 'function' &&
|
|
389
|
+
typeof object.delete === 'function' &&
|
|
390
|
+
typeof object.get === 'function' &&
|
|
391
|
+
typeof object.getAll === 'function' &&
|
|
392
|
+
typeof object.has === 'function' &&
|
|
393
|
+
typeof object.set === 'function' &&
|
|
394
|
+
object[Symbol.toStringTag] === 'FormData'
|
|
392
395
|
)
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
function throwIfAborted (signal) {
|
|
399
|
+
if (!signal) { return }
|
|
400
|
+
if (typeof signal.throwIfAborted === 'function') {
|
|
401
|
+
signal.throwIfAborted()
|
|
402
|
+
} else {
|
|
403
|
+
if (signal.aborted) {
|
|
404
|
+
// DOMException not available < v17.0.0
|
|
405
|
+
const err = new Error('The operation was aborted')
|
|
406
|
+
err.name = 'AbortError'
|
|
407
|
+
throw err
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
395
412
|
const kEnumerableProperty = Object.create(null)
|
|
396
413
|
kEnumerableProperty.enumerable = true
|
|
397
414
|
|
|
@@ -423,6 +440,7 @@ module.exports = {
|
|
|
423
440
|
getSocketInfo,
|
|
424
441
|
isFormDataLike,
|
|
425
442
|
buildURL,
|
|
443
|
+
throwIfAborted,
|
|
426
444
|
nodeMajor,
|
|
427
445
|
nodeMinor,
|
|
428
446
|
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13)
|
package/lib/fetch/dataURL.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const { atob } = require('buffer')
|
|
3
|
-
const { format } = require('url')
|
|
4
3
|
const { isValidHTTPToken, isomorphicDecode } = require('./util')
|
|
5
4
|
|
|
6
5
|
const encoder = new TextEncoder()
|
|
@@ -118,7 +117,17 @@ function dataURLProcessor (dataURL) {
|
|
|
118
117
|
* @param {boolean} excludeFragment
|
|
119
118
|
*/
|
|
120
119
|
function URLSerializer (url, excludeFragment = false) {
|
|
121
|
-
|
|
120
|
+
const href = url.href
|
|
121
|
+
|
|
122
|
+
if (!excludeFragment) {
|
|
123
|
+
return href
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const hash = href.lastIndexOf('#')
|
|
127
|
+
if (hash === -1) {
|
|
128
|
+
return href
|
|
129
|
+
}
|
|
130
|
+
return href.slice(0, hash)
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
package/lib/fetch/index.js
CHANGED
|
@@ -297,7 +297,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
|
|
|
297
297
|
// capability.
|
|
298
298
|
// TODO: given global’s relevant settings object’s cross-origin isolated
|
|
299
299
|
// capability?
|
|
300
|
-
|
|
300
|
+
timingInfo.endTime = coarsenedSharedCurrentTime()
|
|
301
301
|
|
|
302
302
|
// 10. Set response’s timing info to timingInfo.
|
|
303
303
|
response.timingInfo = timingInfo
|
package/lib/fetch/request.js
CHANGED
|
@@ -9,7 +9,8 @@ const util = require('../core/util')
|
|
|
9
9
|
const {
|
|
10
10
|
isValidHTTPToken,
|
|
11
11
|
sameOrigin,
|
|
12
|
-
normalizeMethod
|
|
12
|
+
normalizeMethod,
|
|
13
|
+
makePolicyContainer
|
|
13
14
|
} = require('./util')
|
|
14
15
|
const {
|
|
15
16
|
forbiddenMethods,
|
|
@@ -51,10 +52,14 @@ class Request {
|
|
|
51
52
|
input = webidl.converters.RequestInfo(input)
|
|
52
53
|
init = webidl.converters.RequestInit(init)
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
+
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
|
|
55
56
|
this[kRealm] = {
|
|
56
57
|
settingsObject: {
|
|
57
|
-
baseUrl: getGlobalOrigin()
|
|
58
|
+
baseUrl: getGlobalOrigin(),
|
|
59
|
+
get origin () {
|
|
60
|
+
return this.baseUrl?.origin
|
|
61
|
+
},
|
|
62
|
+
policyContainer: makePolicyContainer()
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -349,14 +354,17 @@ class Request {
|
|
|
349
354
|
if (signal.aborted) {
|
|
350
355
|
ac.abort(signal.reason)
|
|
351
356
|
} else {
|
|
352
|
-
const acRef = new WeakRef(ac)
|
|
353
357
|
const abort = function () {
|
|
354
|
-
|
|
358
|
+
ac.abort(this.reason)
|
|
355
359
|
}
|
|
356
360
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
// Third-party AbortControllers may not work with these.
|
|
362
|
+
// See https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619
|
|
363
|
+
try {
|
|
364
|
+
if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) {
|
|
365
|
+
setMaxListeners(100, signal)
|
|
366
|
+
}
|
|
367
|
+
} catch {}
|
|
360
368
|
|
|
361
369
|
signal.addEventListener('abort', abort, { once: true })
|
|
362
370
|
requestFinalizer.register(this, { signal, abort })
|
package/lib/fetch/util.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { redirectStatus, badPorts, referrerPolicy: referrerPolicyTokens } = require('./constants')
|
|
4
|
+
const { getGlobalOrigin } = require('./global')
|
|
4
5
|
const { performance } = require('perf_hooks')
|
|
5
6
|
const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
|
|
6
7
|
const assert = require('assert')
|
|
@@ -36,9 +37,11 @@ function responseLocationURL (response, requestFragment) {
|
|
|
36
37
|
// `Location` and response’s header list.
|
|
37
38
|
let location = response.headersList.get('location')
|
|
38
39
|
|
|
39
|
-
// 3. If location is a value, then set location to the result of
|
|
40
|
-
// location with response’s URL.
|
|
41
|
-
location
|
|
40
|
+
// 3. If location is a header value, then set location to the result of
|
|
41
|
+
// parsing location with response’s URL.
|
|
42
|
+
if (location !== null && isValidHeaderValue(location)) {
|
|
43
|
+
location = new URL(location, responseURL(response))
|
|
44
|
+
}
|
|
42
45
|
|
|
43
46
|
// 4. If location is a URL whose fragment is null, then set location’s
|
|
44
47
|
// fragment to requestFragment.
|
|
@@ -267,7 +270,7 @@ function appendRequestOriginHeader (request) {
|
|
|
267
270
|
// 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list.
|
|
268
271
|
if (request.responseTainting === 'cors' || request.mode === 'websocket') {
|
|
269
272
|
if (serializedOrigin) {
|
|
270
|
-
request.headersList.append('
|
|
273
|
+
request.headersList.append('origin', serializedOrigin)
|
|
271
274
|
}
|
|
272
275
|
|
|
273
276
|
// 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
|
|
@@ -298,7 +301,7 @@ function appendRequestOriginHeader (request) {
|
|
|
298
301
|
|
|
299
302
|
if (serializedOrigin) {
|
|
300
303
|
// 2. Append (`Origin`, serializedOrigin) to request’s header list.
|
|
301
|
-
request.headersList.append('
|
|
304
|
+
request.headersList.append('origin', serializedOrigin)
|
|
302
305
|
}
|
|
303
306
|
}
|
|
304
307
|
}
|
|
@@ -327,14 +330,17 @@ function createOpaqueTimingInfo (timingInfo) {
|
|
|
327
330
|
|
|
328
331
|
// https://html.spec.whatwg.org/multipage/origin.html#policy-container
|
|
329
332
|
function makePolicyContainer () {
|
|
330
|
-
//
|
|
331
|
-
return {
|
|
333
|
+
// Note: the fetch spec doesn't make use of embedder policy or CSP list
|
|
334
|
+
return {
|
|
335
|
+
referrerPolicy: 'strict-origin-when-cross-origin'
|
|
336
|
+
}
|
|
332
337
|
}
|
|
333
338
|
|
|
334
339
|
// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container
|
|
335
|
-
function clonePolicyContainer () {
|
|
336
|
-
|
|
337
|
-
|
|
340
|
+
function clonePolicyContainer (policyContainer) {
|
|
341
|
+
return {
|
|
342
|
+
referrerPolicy: policyContainer.referrerPolicy
|
|
343
|
+
}
|
|
338
344
|
}
|
|
339
345
|
|
|
340
346
|
// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
|
|
@@ -342,104 +348,76 @@ function determineRequestsReferrer (request) {
|
|
|
342
348
|
// 1. Let policy be request's referrer policy.
|
|
343
349
|
const policy = request.referrerPolicy
|
|
344
350
|
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
// Note: policy cannot (shouldn't) be null or an empty string.
|
|
352
|
+
assert(policy)
|
|
353
|
+
|
|
354
|
+
// 2. Let environment be request’s client.
|
|
349
355
|
|
|
350
|
-
// 2. Let environment be the request client
|
|
351
|
-
const environment = request.client
|
|
352
356
|
let referrerSource = null
|
|
353
357
|
|
|
354
|
-
|
|
355
|
-
* 3, Switch on request’s referrer:
|
|
356
|
-
"client"
|
|
357
|
-
If environment’s global object is a Window object, then
|
|
358
|
-
Let document be the associated Document of environment’s global object.
|
|
359
|
-
If document’s origin is an opaque origin, return no referrer.
|
|
360
|
-
While document is an iframe srcdoc document,
|
|
361
|
-
let document be document’s browsing context’s browsing context container’s node document.
|
|
362
|
-
Let referrerSource be document’s URL.
|
|
363
|
-
|
|
364
|
-
Otherwise, let referrerSource be environment’s creation URL.
|
|
365
|
-
|
|
366
|
-
a URL
|
|
367
|
-
Let referrerSource be request’s referrer.
|
|
368
|
-
*/
|
|
358
|
+
// 3. Switch on request’s referrer:
|
|
369
359
|
if (request.referrer === 'client') {
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
// If document’s origin is an opaque origin, return no referrer.
|
|
375
|
-
if (origin == null || origin === 'null') return 'no-referrer'
|
|
376
|
-
|
|
377
|
-
// Let referrerSource be document’s URL.
|
|
378
|
-
referrerSource = new URL(environment.globalObject.location.href)
|
|
379
|
-
} else {
|
|
380
|
-
// 3(a)(II) If environment's global object is not Window,
|
|
381
|
-
// Let referrerSource be environments creationURL
|
|
382
|
-
if (environment?.globalObject?.location == null) {
|
|
383
|
-
return 'no-referrer'
|
|
384
|
-
}
|
|
360
|
+
// Note: node isn't a browser and doesn't implement document/iframes,
|
|
361
|
+
// so we bypass this step and replace it with our own.
|
|
362
|
+
|
|
363
|
+
const globalOrigin = getGlobalOrigin()
|
|
385
364
|
|
|
386
|
-
|
|
365
|
+
if (!globalOrigin || globalOrigin.origin === 'null') {
|
|
366
|
+
return 'no-referrer'
|
|
387
367
|
}
|
|
368
|
+
|
|
369
|
+
// note: we need to clone it as it's mutated
|
|
370
|
+
referrerSource = new URL(globalOrigin)
|
|
388
371
|
} else if (request.referrer instanceof URL) {
|
|
389
|
-
//
|
|
390
|
-
// referrerSource be requests's referrer.
|
|
372
|
+
// Let referrerSource be request’s referrer.
|
|
391
373
|
referrerSource = request.referrer
|
|
392
|
-
} else {
|
|
393
|
-
// If referrerSource neither client nor instance of URL
|
|
394
|
-
// then return "no-referrer".
|
|
395
|
-
return 'no-referrer'
|
|
396
374
|
}
|
|
397
375
|
|
|
398
|
-
|
|
376
|
+
// 4. Let request’s referrerURL be the result of stripping referrerSource for
|
|
377
|
+
// use as a referrer.
|
|
378
|
+
let referrerURL = stripURLForReferrer(referrerSource)
|
|
399
379
|
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
380
|
+
// 5. Let referrerOrigin be the result of stripping referrerSource for use as
|
|
381
|
+
// a referrer, with the origin-only flag set to true.
|
|
382
|
+
const referrerOrigin = stripURLForReferrer(referrerSource, true)
|
|
383
|
+
|
|
384
|
+
// 6. If the result of serializing referrerURL is a string whose length is
|
|
385
|
+
// greater than 4096, set referrerURL to referrerOrigin.
|
|
386
|
+
if (referrerURL.toString().length > 4096) {
|
|
387
|
+
referrerURL = referrerOrigin
|
|
407
388
|
}
|
|
408
389
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
// 4. Let requests's referrerURL be the result of stripping referrer
|
|
412
|
-
// source for use as referrer (using util function, without origin only)
|
|
413
|
-
const referrerUrl = (temp = stripURLForReferrer(referrerSource)).length > 4096
|
|
414
|
-
// 5. Let referrerOrigin be the result of stripping referrer
|
|
415
|
-
// source for use as referrer (using util function, with originOnly true)
|
|
416
|
-
? (referrerOrigin = stripURLForReferrer(referrerSource, true))
|
|
417
|
-
// 6. If result of seralizing referrerUrl is a string whose length is greater than
|
|
418
|
-
// 4096, then set referrerURL to referrerOrigin
|
|
419
|
-
: temp
|
|
420
|
-
const areSameOrigin = sameOrigin(request, referrerUrl)
|
|
421
|
-
const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerUrl) &&
|
|
390
|
+
const areSameOrigin = sameOrigin(request, referrerURL)
|
|
391
|
+
const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) &&
|
|
422
392
|
!isURLPotentiallyTrustworthy(request.url)
|
|
423
393
|
|
|
424
|
-
// NOTE: How to treat step 7?
|
|
425
394
|
// 8. Execute the switch statements corresponding to the value of policy:
|
|
426
395
|
switch (policy) {
|
|
427
396
|
case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true)
|
|
428
|
-
case 'unsafe-url': return
|
|
397
|
+
case 'unsafe-url': return referrerURL
|
|
429
398
|
case 'same-origin':
|
|
430
399
|
return areSameOrigin ? referrerOrigin : 'no-referrer'
|
|
431
400
|
case 'origin-when-cross-origin':
|
|
432
|
-
return areSameOrigin ?
|
|
433
|
-
case 'strict-origin-when-cross-origin':
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
//
|
|
401
|
+
return areSameOrigin ? referrerURL : referrerOrigin
|
|
402
|
+
case 'strict-origin-when-cross-origin': {
|
|
403
|
+
const currentURL = requestCurrentURL(request)
|
|
404
|
+
|
|
405
|
+
// 1. If the origin of referrerURL and the origin of request’s current
|
|
406
|
+
// URL are the same, then return referrerURL.
|
|
407
|
+
if (sameOrigin(referrerURL, currentURL)) {
|
|
408
|
+
return referrerURL
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 2. If referrerURL is a potentially trustworthy URL and request’s
|
|
412
|
+
// current URL is not a potentially trustworthy URL, then return no
|
|
413
|
+
// referrer.
|
|
414
|
+
if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) {
|
|
415
|
+
return 'no-referrer'
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 3. Return referrerOrigin.
|
|
419
|
+
return referrerOrigin
|
|
420
|
+
}
|
|
443
421
|
case 'strict-origin': // eslint-disable-line
|
|
444
422
|
/**
|
|
445
423
|
* 1. If referrerURL is a potentially trustworthy URL and
|
|
@@ -458,15 +436,42 @@ function determineRequestsReferrer (request) {
|
|
|
458
436
|
default: // eslint-disable-line
|
|
459
437
|
return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin
|
|
460
438
|
}
|
|
439
|
+
}
|
|
461
440
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
441
|
+
/**
|
|
442
|
+
* @see https://w3c.github.io/webappsec-referrer-policy/#strip-url
|
|
443
|
+
* @param {URL} url
|
|
444
|
+
* @param {boolean|undefined} originOnly
|
|
445
|
+
*/
|
|
446
|
+
function stripURLForReferrer (url, originOnly) {
|
|
447
|
+
// 1. Assert: url is a URL.
|
|
448
|
+
assert(url instanceof URL)
|
|
467
449
|
|
|
468
|
-
|
|
450
|
+
// 2. If url’s scheme is a local scheme, then return no referrer.
|
|
451
|
+
if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') {
|
|
452
|
+
return 'no-referrer'
|
|
469
453
|
}
|
|
454
|
+
|
|
455
|
+
// 3. Set url’s username to the empty string.
|
|
456
|
+
url.username = ''
|
|
457
|
+
|
|
458
|
+
// 4. Set url’s password to the empty string.
|
|
459
|
+
url.password = ''
|
|
460
|
+
|
|
461
|
+
// 5. Set url’s fragment to null.
|
|
462
|
+
url.hash = ''
|
|
463
|
+
|
|
464
|
+
// 6. If the origin-only flag is true, then:
|
|
465
|
+
if (originOnly) {
|
|
466
|
+
// 1. Set url’s path to « the empty string ».
|
|
467
|
+
url.pathname = ''
|
|
468
|
+
|
|
469
|
+
// 2. Set url’s query to null.
|
|
470
|
+
url.search = ''
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 7. Return url.
|
|
474
|
+
return url
|
|
470
475
|
}
|
|
471
476
|
|
|
472
477
|
function isURLPotentiallyTrustworthy (url) {
|
package/lib/fileapi/encoding.js
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @see https://encoding.spec.whatwg.org/#concept-encoding-get
|
|
5
|
-
* @param {string} label
|
|
5
|
+
* @param {string|undefined} label
|
|
6
6
|
*/
|
|
7
7
|
function getEncoding (label) {
|
|
8
|
+
if (!label) {
|
|
9
|
+
return 'failure'
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
// 1. Remove any leading and trailing ASCII whitespace from label.
|
|
9
13
|
// 2. If label is an ASCII case-insensitive match for any of the
|
|
10
14
|
// labels listed in the table below, then return the
|
package/lib/proxy-agent.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { kProxy, kClose, kDestroy, kInterceptors } = require('./core/symbols')
|
|
4
4
|
const { URL } = require('url')
|
|
5
5
|
const Agent = require('./agent')
|
|
6
|
-
const
|
|
6
|
+
const Pool = require('./pool')
|
|
7
7
|
const DispatcherBase = require('./dispatcher-base')
|
|
8
8
|
const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
|
|
9
9
|
const buildConnector = require('./core/connect')
|
|
@@ -34,6 +34,10 @@ function buildProxyOptions (opts) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function defaultFactory (origin, opts) {
|
|
38
|
+
return new Pool(origin, opts)
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
class ProxyAgent extends DispatcherBase {
|
|
38
42
|
constructor (opts) {
|
|
39
43
|
super(opts)
|
|
@@ -51,6 +55,12 @@ class ProxyAgent extends DispatcherBase {
|
|
|
51
55
|
throw new InvalidArgumentError('Proxy opts.uri is mandatory')
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
const { clientFactory = defaultFactory } = opts
|
|
59
|
+
|
|
60
|
+
if (typeof clientFactory !== 'function') {
|
|
61
|
+
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
this[kRequestTls] = opts.requestTls
|
|
55
65
|
this[kProxyTls] = opts.proxyTls
|
|
56
66
|
this[kProxyHeaders] = opts.headers || {}
|
|
@@ -69,7 +79,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
69
79
|
|
|
70
80
|
const connect = buildConnector({ ...opts.proxyTls })
|
|
71
81
|
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
|
|
72
|
-
this[kClient] =
|
|
82
|
+
this[kClient] = clientFactory(resolvedUrl, { connect })
|
|
73
83
|
this[kAgent] = new Agent({
|
|
74
84
|
...opts,
|
|
75
85
|
connect: async (opts, callback) => {
|
package/lib/timers.js
CHANGED
|
@@ -13,13 +13,15 @@ function onTimeout () {
|
|
|
13
13
|
while (idx < len) {
|
|
14
14
|
const timer = fastTimers[idx]
|
|
15
15
|
|
|
16
|
-
if (timer.
|
|
17
|
-
timer.
|
|
16
|
+
if (timer.state === 0) {
|
|
17
|
+
timer.state = fastNow + timer.delay
|
|
18
|
+
} else if (timer.state > 0 && fastNow >= timer.state) {
|
|
19
|
+
timer.state = -1
|
|
18
20
|
timer.callback(timer.opaque)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
if (timer.
|
|
22
|
-
timer.
|
|
23
|
+
if (timer.state === -1) {
|
|
24
|
+
timer.state = -2
|
|
23
25
|
if (idx !== len - 1) {
|
|
24
26
|
fastTimers[idx] = fastTimers.pop()
|
|
25
27
|
} else {
|
|
@@ -53,37 +55,43 @@ class Timeout {
|
|
|
53
55
|
this.callback = callback
|
|
54
56
|
this.delay = delay
|
|
55
57
|
this.opaque = opaque
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
|
|
59
|
+
// -2 not in timer list
|
|
60
|
+
// -1 in timer list but inactive
|
|
61
|
+
// 0 in timer list waiting for time
|
|
62
|
+
// > 0 in timer list waiting for time to expire
|
|
63
|
+
this.state = -2
|
|
58
64
|
|
|
59
65
|
this.refresh()
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
refresh () {
|
|
63
|
-
if (
|
|
64
|
-
this.active = true
|
|
69
|
+
if (this.state === -2) {
|
|
65
70
|
fastTimers.push(this)
|
|
66
71
|
if (!fastNowTimeout || fastTimers.length === 1) {
|
|
67
72
|
refreshTimeout()
|
|
68
|
-
fastNow = Date.now()
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
this.
|
|
76
|
+
this.state = 0
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
clear () {
|
|
76
|
-
this.
|
|
80
|
+
this.state = -1
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
module.exports = {
|
|
81
85
|
setTimeout (callback, delay, opaque) {
|
|
82
|
-
return
|
|
86
|
+
return delay < 1e3
|
|
87
|
+
? setTimeout(callback, delay, opaque)
|
|
88
|
+
: new Timeout(callback, delay, opaque)
|
|
83
89
|
},
|
|
84
90
|
clearTimeout (timeout) {
|
|
85
|
-
if (timeout
|
|
91
|
+
if (timeout instanceof Timeout) {
|
|
86
92
|
timeout.clear()
|
|
93
|
+
} else {
|
|
94
|
+
clearTimeout(timeout)
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
}
|
|
@@ -5,19 +5,15 @@ const diagnosticsChannel = require('diagnostics_channel')
|
|
|
5
5
|
const { uid, states } = require('./constants')
|
|
6
6
|
const {
|
|
7
7
|
kReadyState,
|
|
8
|
-
kResponse,
|
|
9
|
-
kExtensions,
|
|
10
|
-
kProtocol,
|
|
11
8
|
kSentClose,
|
|
12
9
|
kByteParser,
|
|
13
10
|
kReceivedClose
|
|
14
11
|
} = require('./symbols')
|
|
15
12
|
const { fireEvent, failWebsocketConnection } = require('./util')
|
|
16
13
|
const { CloseEvent } = require('./events')
|
|
17
|
-
const { ByteParser } = require('./receiver')
|
|
18
14
|
const { makeRequest } = require('../fetch/request')
|
|
19
15
|
const { fetching } = require('../fetch/index')
|
|
20
|
-
const { getGlobalDispatcher } = require('
|
|
16
|
+
const { getGlobalDispatcher } = require('../global')
|
|
21
17
|
|
|
22
18
|
const channels = {}
|
|
23
19
|
channels.open = diagnosticsChannel.channel('undici:websocket:open')
|
|
@@ -29,8 +25,9 @@ channels.socketError = diagnosticsChannel.channel('undici:websocket:socket_error
|
|
|
29
25
|
* @param {URL} url
|
|
30
26
|
* @param {string|string[]} protocols
|
|
31
27
|
* @param {import('./websocket').WebSocket} ws
|
|
28
|
+
* @param {(response: any) => void} onEstablish
|
|
32
29
|
*/
|
|
33
|
-
function establishWebSocketConnection (url, protocols, ws) {
|
|
30
|
+
function establishWebSocketConnection (url, protocols, ws, onEstablish) {
|
|
34
31
|
// 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s
|
|
35
32
|
// scheme is "ws", and to "https" otherwise.
|
|
36
33
|
const requestURL = url
|
|
@@ -173,67 +170,25 @@ function establishWebSocketConnection (url, protocols, ws) {
|
|
|
173
170
|
return
|
|
174
171
|
}
|
|
175
172
|
|
|
176
|
-
// processResponse is called when the "response’s header list has been received and initialized."
|
|
177
|
-
// once this happens, the connection is open
|
|
178
|
-
ws[kResponse] = response
|
|
179
|
-
|
|
180
|
-
const parser = new ByteParser(ws)
|
|
181
|
-
response.socket.ws = ws // TODO: use symbol
|
|
182
|
-
ws[kByteParser] = parser
|
|
183
|
-
|
|
184
|
-
whenConnectionEstablished(ws)
|
|
185
|
-
|
|
186
173
|
response.socket.on('data', onSocketData)
|
|
187
174
|
response.socket.on('close', onSocketClose)
|
|
188
175
|
response.socket.on('error', onSocketError)
|
|
189
176
|
|
|
190
|
-
|
|
177
|
+
if (channels.open.hasSubscribers) {
|
|
178
|
+
channels.open.publish({
|
|
179
|
+
address: response.socket.address(),
|
|
180
|
+
protocol: secProtocol,
|
|
181
|
+
extensions: secExtension
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onEstablish(response)
|
|
191
186
|
}
|
|
192
187
|
})
|
|
193
188
|
|
|
194
189
|
return controller
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
/**
|
|
198
|
-
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|
199
|
-
* @param {import('./websocket').WebSocket} ws
|
|
200
|
-
*/
|
|
201
|
-
function whenConnectionEstablished (ws) {
|
|
202
|
-
const { [kResponse]: response } = ws
|
|
203
|
-
|
|
204
|
-
// 1. Change the ready state to OPEN (1).
|
|
205
|
-
ws[kReadyState] = states.OPEN
|
|
206
|
-
|
|
207
|
-
// 2. Change the extensions attribute’s value to the extensions in use, if
|
|
208
|
-
// it is not the null value.
|
|
209
|
-
// https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
|
|
210
|
-
const extensions = response.headersList.get('sec-websocket-extensions')
|
|
211
|
-
|
|
212
|
-
if (extensions !== null) {
|
|
213
|
-
ws[kExtensions] = extensions
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 3. Change the protocol attribute’s value to the subprotocol in use, if
|
|
217
|
-
// it is not the null value.
|
|
218
|
-
// https://datatracker.ietf.org/doc/html/rfc6455#section-1.9
|
|
219
|
-
const protocol = response.headersList.get('sec-websocket-protocol')
|
|
220
|
-
|
|
221
|
-
if (protocol !== null) {
|
|
222
|
-
ws[kProtocol] = protocol
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 4. Fire an event named open at the WebSocket object.
|
|
226
|
-
fireEvent('open', ws)
|
|
227
|
-
|
|
228
|
-
if (channels.open.hasSubscribers) {
|
|
229
|
-
channels.open.publish({
|
|
230
|
-
address: response.socket.address(),
|
|
231
|
-
protocol,
|
|
232
|
-
extensions
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
192
|
/**
|
|
238
193
|
* @param {Buffer} chunk
|
|
239
194
|
*/
|
|
@@ -243,10 +198,6 @@ function onSocketData (chunk) {
|
|
|
243
198
|
}
|
|
244
199
|
}
|
|
245
200
|
|
|
246
|
-
function onParserDrain () {
|
|
247
|
-
this.ws[kResponse].socket.resume()
|
|
248
|
-
}
|
|
249
|
-
|
|
250
201
|
/**
|
|
251
202
|
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|
252
203
|
* @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
|
package/lib/websocket/symbols.js
CHANGED
|
@@ -5,10 +5,7 @@ module.exports = {
|
|
|
5
5
|
kReadyState: Symbol('ready state'),
|
|
6
6
|
kController: Symbol('controller'),
|
|
7
7
|
kResponse: Symbol('response'),
|
|
8
|
-
kExtensions: Symbol('extensions'),
|
|
9
|
-
kProtocol: Symbol('protocol'),
|
|
10
8
|
kBinaryType: Symbol('binary type'),
|
|
11
|
-
kClosingFrame: Symbol('closing frame'),
|
|
12
9
|
kSentClose: Symbol('sent close'),
|
|
13
10
|
kReceivedClose: Symbol('received close'),
|
|
14
11
|
kByteParser: Symbol('byte parser')
|
|
@@ -8,15 +8,15 @@ const {
|
|
|
8
8
|
kWebSocketURL,
|
|
9
9
|
kReadyState,
|
|
10
10
|
kController,
|
|
11
|
-
kExtensions,
|
|
12
|
-
kProtocol,
|
|
13
11
|
kBinaryType,
|
|
14
12
|
kResponse,
|
|
15
|
-
kSentClose
|
|
13
|
+
kSentClose,
|
|
14
|
+
kByteParser
|
|
16
15
|
} = require('./symbols')
|
|
17
|
-
const { isEstablished, isClosing, isValidSubprotocol, failWebsocketConnection } = require('./util')
|
|
16
|
+
const { isEstablished, isClosing, isValidSubprotocol, failWebsocketConnection, fireEvent } = require('./util')
|
|
18
17
|
const { establishWebSocketConnection } = require('./connection')
|
|
19
18
|
const { WebsocketFrameSend } = require('./frame')
|
|
19
|
+
const { ByteParser } = require('./receiver')
|
|
20
20
|
const { kEnumerableProperty, isBlobLike } = require('../core/util')
|
|
21
21
|
const { types } = require('util')
|
|
22
22
|
|
|
@@ -32,6 +32,8 @@ class WebSocket extends EventTarget {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
#bufferedAmount = 0
|
|
35
|
+
#protocol = ''
|
|
36
|
+
#extensions = ''
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* @param {string} url
|
|
@@ -104,7 +106,12 @@ class WebSocket extends EventTarget {
|
|
|
104
106
|
|
|
105
107
|
// 1. Establish a WebSocket connection given urlRecord, protocols,
|
|
106
108
|
// and client.
|
|
107
|
-
this[kController] = establishWebSocketConnection(
|
|
109
|
+
this[kController] = establishWebSocketConnection(
|
|
110
|
+
urlRecord,
|
|
111
|
+
protocols,
|
|
112
|
+
this,
|
|
113
|
+
(response) => this.#onConnectionEstablished(response)
|
|
114
|
+
)
|
|
108
115
|
|
|
109
116
|
// Each WebSocket object has an associated ready state, which is a
|
|
110
117
|
// number representing the state of the connection. Initially it must
|
|
@@ -112,10 +119,8 @@ class WebSocket extends EventTarget {
|
|
|
112
119
|
this[kReadyState] = WebSocket.CONNECTING
|
|
113
120
|
|
|
114
121
|
// The extensions attribute must initially return the empty string.
|
|
115
|
-
this[kExtensions] = ''
|
|
116
122
|
|
|
117
123
|
// The protocol attribute must initially return the empty string.
|
|
118
|
-
this[kProtocol] = ''
|
|
119
124
|
|
|
120
125
|
// Each WebSocket object has an associated binary type, which is a
|
|
121
126
|
// BinaryType. Initially it must be "blob".
|
|
@@ -368,13 +373,13 @@ class WebSocket extends EventTarget {
|
|
|
368
373
|
get extensions () {
|
|
369
374
|
webidl.brandCheck(this, WebSocket)
|
|
370
375
|
|
|
371
|
-
return this
|
|
376
|
+
return this.#extensions
|
|
372
377
|
}
|
|
373
378
|
|
|
374
379
|
get protocol () {
|
|
375
380
|
webidl.brandCheck(this, WebSocket)
|
|
376
381
|
|
|
377
|
-
return this
|
|
382
|
+
return this.#protocol
|
|
378
383
|
}
|
|
379
384
|
|
|
380
385
|
get onopen () {
|
|
@@ -476,6 +481,47 @@ class WebSocket extends EventTarget {
|
|
|
476
481
|
this[kBinaryType] = type
|
|
477
482
|
}
|
|
478
483
|
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
|
|
487
|
+
*/
|
|
488
|
+
#onConnectionEstablished (response) {
|
|
489
|
+
// processResponse is called when the "response’s header list has been received and initialized."
|
|
490
|
+
// once this happens, the connection is open
|
|
491
|
+
this[kResponse] = response
|
|
492
|
+
|
|
493
|
+
const parser = new ByteParser(this)
|
|
494
|
+
parser.on('drain', function onParserDrain () {
|
|
495
|
+
this.ws[kResponse].socket.resume()
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
response.socket.ws = this
|
|
499
|
+
this[kByteParser] = parser
|
|
500
|
+
|
|
501
|
+
// 1. Change the ready state to OPEN (1).
|
|
502
|
+
this[kReadyState] = states.OPEN
|
|
503
|
+
|
|
504
|
+
// 2. Change the extensions attribute’s value to the extensions in use, if
|
|
505
|
+
// it is not the null value.
|
|
506
|
+
// https://datatracker.ietf.org/doc/html/rfc6455#section-9.1
|
|
507
|
+
const extensions = response.headersList.get('sec-websocket-extensions')
|
|
508
|
+
|
|
509
|
+
if (extensions !== null) {
|
|
510
|
+
this.#extensions = extensions
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// 3. Change the protocol attribute’s value to the subprotocol in use, if
|
|
514
|
+
// it is not the null value.
|
|
515
|
+
// https://datatracker.ietf.org/doc/html/rfc6455#section-1.9
|
|
516
|
+
const protocol = response.headersList.get('sec-websocket-protocol')
|
|
517
|
+
|
|
518
|
+
if (protocol !== null) {
|
|
519
|
+
this.#protocol = protocol
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// 4. Fire an event named open at the WebSocket object.
|
|
523
|
+
fireEvent('open', this)
|
|
524
|
+
}
|
|
479
525
|
}
|
|
480
526
|
|
|
481
527
|
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.21.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": {
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"husky": "^8.0.1",
|
|
87
87
|
"import-fresh": "^3.3.0",
|
|
88
88
|
"jest": "^29.0.2",
|
|
89
|
+
"jsdom": "^21.1.0",
|
|
89
90
|
"jsfuzz": "^1.0.15",
|
|
90
91
|
"mocha": "^10.0.0",
|
|
91
92
|
"p-timeout": "^3.2.0",
|
|
@@ -112,8 +113,7 @@
|
|
|
112
113
|
"ignore": [
|
|
113
114
|
"lib/llhttp/constants.js",
|
|
114
115
|
"lib/llhttp/utils.js",
|
|
115
|
-
"test/wpt/tests"
|
|
116
|
-
"test/wpt/runner/resources"
|
|
116
|
+
"test/wpt/tests"
|
|
117
117
|
]
|
|
118
118
|
},
|
|
119
119
|
"tsd": {
|
package/types/balanced-pool.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ import { URL } from 'url'
|
|
|
5
5
|
export default BalancedPool
|
|
6
6
|
|
|
7
7
|
declare class BalancedPool extends Dispatcher {
|
|
8
|
-
constructor(url: string | URL |
|
|
8
|
+
constructor(url: string | string[] | URL | URL[], options?: Pool.Options);
|
|
9
9
|
|
|
10
|
-
addUpstream(upstream: string): BalancedPool;
|
|
11
|
-
removeUpstream(upstream: string): BalancedPool;
|
|
10
|
+
addUpstream(upstream: string | URL): BalancedPool;
|
|
11
|
+
removeUpstream(upstream: string | URL): BalancedPool;
|
|
12
12
|
upstreams: Array<string>;
|
|
13
13
|
|
|
14
14
|
/** `true` after `pool.close()` has been called. */
|
package/types/client.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ import Dispatcher from './dispatcher'
|
|
|
4
4
|
import DispatchInterceptor from './dispatcher'
|
|
5
5
|
import buildConnector from "./connector";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A basic HTTP/1.1 client, mapped on top a single TCP/TLS connection. Pipelining is disabled by default.
|
|
9
|
+
*/
|
|
10
|
+
export class Client extends Dispatcher {
|
|
11
11
|
constructor(url: string | URL, options?: Client.Options);
|
|
12
12
|
/** Property to get and set the pipelining factor. */
|
|
13
13
|
pipelining: number;
|
|
@@ -17,40 +17,62 @@ declare class Client extends Dispatcher {
|
|
|
17
17
|
destroyed: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
declare namespace Client {
|
|
20
|
+
export declare namespace Client {
|
|
21
|
+
export interface OptionsInterceptors {
|
|
22
|
+
Client: readonly DispatchInterceptor[];
|
|
23
|
+
}
|
|
21
24
|
export interface Options {
|
|
25
|
+
/** TODO */
|
|
26
|
+
interceptors?: OptionsInterceptors;
|
|
27
|
+
/** The maximum length of request headers in bytes. Default: `16384` (16KiB). */
|
|
28
|
+
maxHeaderSize?: number;
|
|
29
|
+
/** The amount of time the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default: `300e3` milliseconds (300s). */
|
|
30
|
+
headersTimeout?: number;
|
|
31
|
+
/** @deprecated unsupported socketTimeout, use headersTimeout & bodyTimeout instead */
|
|
32
|
+
socketTimeout?: never;
|
|
33
|
+
/** @deprecated unsupported requestTimeout, use headersTimeout & bodyTimeout instead */
|
|
34
|
+
requestTimeout?: never;
|
|
35
|
+
/** TODO */
|
|
36
|
+
connectTimeout?: number;
|
|
37
|
+
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Default: `300e3` milliseconds (300s). */
|
|
38
|
+
bodyTimeout?: number;
|
|
39
|
+
/** @deprecated unsupported idleTimeout, use keepAliveTimeout instead */
|
|
40
|
+
idleTimeout?: never;
|
|
41
|
+
/** @deprecated unsupported keepAlive, use pipelining=0 instead */
|
|
42
|
+
keepAlive?: never;
|
|
22
43
|
/** the timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. Default: `4e3` milliseconds (4s). */
|
|
23
|
-
keepAliveTimeout?: number
|
|
44
|
+
keepAliveTimeout?: number;
|
|
45
|
+
/** @deprecated unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead */
|
|
46
|
+
maxKeepAliveTimeout?: never;
|
|
24
47
|
/** the maximum allowed `idleTimeout` when overridden by *keep-alive* hints from the server. Default: `600e3` milliseconds (10min). */
|
|
25
|
-
keepAliveMaxTimeout?: number
|
|
48
|
+
keepAliveMaxTimeout?: number;
|
|
26
49
|
/** A number subtracted from server *keep-alive* hints when overriding `idleTimeout` to account for timing inaccuracies caused by e.g. transport latency. Default: `1e3` milliseconds (1s). */
|
|
27
|
-
keepAliveTimeoutThreshold?: number
|
|
50
|
+
keepAliveTimeoutThreshold?: number;
|
|
51
|
+
/** TODO */
|
|
52
|
+
socketPath?: string;
|
|
28
53
|
/** The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Default: `1`. */
|
|
29
|
-
pipelining?: number
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
/** The maximum length of request headers in bytes. Default: `16384` (16KiB). */
|
|
33
|
-
maxHeaderSize?: number | null;
|
|
34
|
-
/** The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Default: `300e3` milliseconds (300s). */
|
|
35
|
-
bodyTimeout?: number | null;
|
|
36
|
-
/** The amount of time the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default: `300e3` milliseconds (300s). */
|
|
37
|
-
headersTimeout?: number | null;
|
|
54
|
+
pipelining?: number;
|
|
55
|
+
/** @deprecated use the connect option instead */
|
|
56
|
+
tls?: never;
|
|
38
57
|
/** If `true`, an error is thrown when the request content-length header doesn't match the length of the request body. Default: `true`. */
|
|
39
58
|
strictContentLength?: boolean;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
/** */
|
|
59
|
+
/** TODO */
|
|
60
|
+
maxCachedSessions?: number;
|
|
61
|
+
/** TODO */
|
|
62
|
+
maxRedirections?: number;
|
|
63
|
+
/** TODO */
|
|
64
|
+
connect?: buildConnector.BuildOptions | buildConnector.connector;
|
|
65
|
+
/** TODO */
|
|
43
66
|
maxRequestsPerClient?: number;
|
|
67
|
+
/** TODO */
|
|
68
|
+
localAddress?: string;
|
|
44
69
|
/** Max response body size in bytes, -1 is disabled */
|
|
45
|
-
maxResponseSize?: number
|
|
70
|
+
maxResponseSize?: number;
|
|
46
71
|
/** Enables a family autodetection algorithm that loosely implements section 5 of RFC 8305. */
|
|
47
72
|
autoSelectFamily?: boolean;
|
|
48
73
|
/** The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. */
|
|
49
|
-
autoSelectFamilyAttemptTimeout?: number;
|
|
50
|
-
|
|
51
|
-
interceptors?: {Client: readonly DispatchInterceptor[] | undefined}
|
|
74
|
+
autoSelectFamilyAttemptTimeout?: number;
|
|
52
75
|
}
|
|
53
|
-
|
|
54
76
|
export interface SocketInfo {
|
|
55
77
|
localAddress?: string
|
|
56
78
|
localPort?: number
|
|
@@ -61,6 +83,6 @@ declare namespace Client {
|
|
|
61
83
|
bytesWritten?: number
|
|
62
84
|
bytesRead?: number
|
|
63
85
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
86
|
}
|
|
87
|
+
|
|
88
|
+
export default Client;
|
package/types/proxy-agent.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import Agent from './agent'
|
|
2
2
|
import buildConnector from './connector';
|
|
3
|
+
import Client from './client'
|
|
3
4
|
import Dispatcher from './dispatcher'
|
|
4
5
|
import { IncomingHttpHeaders } from './header'
|
|
6
|
+
import Pool from './pool'
|
|
5
7
|
|
|
6
8
|
export default ProxyAgent
|
|
7
9
|
|
|
@@ -23,5 +25,6 @@ declare namespace ProxyAgent {
|
|
|
23
25
|
headers?: IncomingHttpHeaders;
|
|
24
26
|
requestTls?: buildConnector.BuildOptions;
|
|
25
27
|
proxyTls?: buildConnector.BuildOptions;
|
|
28
|
+
clientFactory?(origin: URL, opts: object): Dispatcher;
|
|
26
29
|
}
|
|
27
30
|
}
|