undici 7.10.0 → 7.12.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 +159 -0
- package/docs/docs/api/CacheStore.md +3 -3
- package/docs/docs/api/Debug.md +13 -13
- package/docs/docs/api/DiagnosticsChannel.md +32 -4
- package/docs/docs/api/Dispatcher.md +22 -3
- package/docs/docs/api/GlobalInstallation.md +91 -0
- package/docs/docs/api/MockClient.md +4 -0
- package/docs/docs/api/MockPool.md +6 -0
- package/docs/docs/api/ProxyAgent.md +2 -0
- package/docs/docs/api/RetryAgent.md +6 -1
- package/docs/docs/api/RetryHandler.md +1 -0
- package/docs/docs/api/WebSocket.md +27 -0
- package/index.js +18 -1
- package/lib/api/api-stream.js +1 -1
- package/lib/api/readable.js +1 -3
- package/lib/cache/memory-cache-store.js +3 -3
- package/lib/cache/sqlite-cache-store.js +1 -1
- package/lib/core/connect.js +21 -51
- package/lib/core/diagnostics.js +6 -4
- package/lib/core/request.js +12 -1
- package/lib/core/tree.js +1 -1
- package/lib/core/util.js +0 -45
- package/lib/dispatcher/client-h1.js +9 -18
- package/lib/dispatcher/proxy-agent.js +2 -1
- package/lib/handler/cache-handler.js +4 -1
- package/lib/handler/redirect-handler.js +2 -2
- package/lib/handler/retry-handler.js +110 -56
- package/lib/interceptor/cache.js +2 -2
- package/lib/interceptor/redirect.js +1 -1
- package/lib/mock/mock-client.js +4 -0
- package/lib/mock/mock-pool.js +4 -0
- package/lib/util/cache.js +12 -2
- package/lib/util/promise.js +28 -0
- package/lib/util/timers.js +11 -9
- package/lib/web/cache/cache.js +11 -9
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +1 -1
- package/lib/web/eventsource/eventsource.js +3 -6
- package/lib/web/eventsource/util.js +1 -1
- package/lib/web/fetch/body.js +36 -24
- package/lib/web/fetch/formdata-parser.js +4 -4
- package/lib/web/fetch/formdata.js +1 -1
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +228 -226
- package/lib/web/fetch/request.js +16 -8
- package/lib/web/fetch/response.js +6 -4
- package/lib/web/fetch/util.js +23 -25
- package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
- package/lib/web/websocket/connection.js +4 -12
- package/lib/web/websocket/events.js +1 -1
- package/lib/web/websocket/frame.js +2 -1
- package/lib/web/websocket/receiver.js +2 -12
- package/lib/web/websocket/stream/websocketerror.js +1 -1
- package/lib/web/websocket/stream/websocketstream.js +8 -5
- package/lib/web/websocket/websocket.js +61 -5
- package/package.json +5 -5
- package/types/diagnostics-channel.d.ts +9 -0
- package/types/dispatcher.d.ts +3 -2
- package/types/env-http-proxy-agent.d.ts +2 -1
- package/types/eventsource.d.ts +3 -3
- package/types/fetch.d.ts +1 -0
- package/types/handlers.d.ts +1 -1
- package/types/mock-client.d.ts +2 -0
- package/types/mock-interceptor.d.ts +2 -0
- package/types/mock-pool.d.ts +2 -0
- package/types/retry-handler.d.ts +9 -0
- package/types/webidl.d.ts +29 -15
- package/types/websocket.d.ts +3 -1
- package/lib/web/fetch/dispatcher-weakref.js +0 -46
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
|
|
4
|
-
const { extractBody, cloneBody, mixinBody,
|
|
4
|
+
const { extractBody, cloneBody, mixinBody, streamRegistry, bodyUnusable } = require('./body')
|
|
5
5
|
const util = require('../../core/util')
|
|
6
6
|
const nodeUtil = require('node:util')
|
|
7
7
|
const { kEnumerableProperty } = util
|
|
@@ -18,7 +18,7 @@ const {
|
|
|
18
18
|
redirectStatusSet,
|
|
19
19
|
nullBodyStatus
|
|
20
20
|
} = require('./constants')
|
|
21
|
-
const { webidl } = require('
|
|
21
|
+
const { webidl } = require('../webidl')
|
|
22
22
|
const { URLSerializer } = require('./data-url')
|
|
23
23
|
const { kConstruct } = require('../../core/symbols')
|
|
24
24
|
const assert = require('node:assert')
|
|
@@ -352,7 +352,9 @@ function cloneResponse (response) {
|
|
|
352
352
|
// 3. If response’s body is non-null, then set newResponse’s body to the
|
|
353
353
|
// result of cloning response’s body.
|
|
354
354
|
if (response.body != null) {
|
|
355
|
-
newResponse.body = cloneBody(
|
|
355
|
+
newResponse.body = cloneBody(response.body)
|
|
356
|
+
|
|
357
|
+
streamRegistry.register(newResponse, new WeakRef(response.body.stream))
|
|
356
358
|
}
|
|
357
359
|
|
|
358
360
|
// 4. Return newResponse.
|
|
@@ -552,7 +554,7 @@ function fromInnerResponse (innerResponse, guard) {
|
|
|
552
554
|
setHeadersList(headers, innerResponse.headersList)
|
|
553
555
|
setHeadersGuard(headers, guard)
|
|
554
556
|
|
|
555
|
-
if (
|
|
557
|
+
if (innerResponse.body?.stream) {
|
|
556
558
|
// If the target (response) is reclaimed, the cleanup callback may be called at some point with
|
|
557
559
|
// the held value provided for it (innerResponse.body.stream). The held value can be any value:
|
|
558
560
|
// a primitive or an object, even undefined. If the held value is an object, the registry keeps
|
package/lib/web/fetch/util.js
CHANGED
|
@@ -9,7 +9,7 @@ const { performance } = require('node:perf_hooks')
|
|
|
9
9
|
const { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
|
|
10
10
|
const assert = require('node:assert')
|
|
11
11
|
const { isUint8Array } = require('node:util/types')
|
|
12
|
-
const { webidl } = require('
|
|
12
|
+
const { webidl } = require('../webidl')
|
|
13
13
|
|
|
14
14
|
let supportedHashes = []
|
|
15
15
|
|
|
@@ -924,17 +924,6 @@ function sameOrigin (A, B) {
|
|
|
924
924
|
return false
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
-
function createDeferredPromise () {
|
|
928
|
-
let res
|
|
929
|
-
let rej
|
|
930
|
-
const promise = new Promise((resolve, reject) => {
|
|
931
|
-
res = resolve
|
|
932
|
-
rej = reject
|
|
933
|
-
})
|
|
934
|
-
|
|
935
|
-
return { promise, resolve: res, reject: rej }
|
|
936
|
-
}
|
|
937
|
-
|
|
938
927
|
function isAborted (fetchParams) {
|
|
939
928
|
return fetchParams.controller.state === 'aborted'
|
|
940
929
|
}
|
|
@@ -1177,6 +1166,11 @@ function iteratorMixin (name, object, kInternalIterator, keyIndex = 0, valueInde
|
|
|
1177
1166
|
}
|
|
1178
1167
|
|
|
1179
1168
|
/**
|
|
1169
|
+
* @param {import('./body').ExtractBodyResult} body
|
|
1170
|
+
* @param {(bytes: Uint8Array) => void} processBody
|
|
1171
|
+
* @param {(error: Error) => void} processBodyError
|
|
1172
|
+
* @returns {void}
|
|
1173
|
+
*
|
|
1180
1174
|
* @see https://fetch.spec.whatwg.org/#body-fully-read
|
|
1181
1175
|
*/
|
|
1182
1176
|
function fullyReadBody (body, processBody, processBodyError) {
|
|
@@ -1191,20 +1185,17 @@ function fullyReadBody (body, processBody, processBodyError) {
|
|
|
1191
1185
|
// with taskDestination.
|
|
1192
1186
|
const errorSteps = processBodyError
|
|
1193
1187
|
|
|
1188
|
+
try {
|
|
1194
1189
|
// 4. Let reader be the result of getting a reader for body’s stream.
|
|
1195
1190
|
// If that threw an exception, then run errorSteps with that
|
|
1196
1191
|
// exception and return.
|
|
1197
|
-
|
|
1192
|
+
const reader = body.stream.getReader()
|
|
1198
1193
|
|
|
1199
|
-
|
|
1200
|
-
reader
|
|
1194
|
+
// 5. Read all bytes from reader, given successSteps and errorSteps.
|
|
1195
|
+
readAllBytes(reader, successSteps, errorSteps)
|
|
1201
1196
|
} catch (e) {
|
|
1202
1197
|
errorSteps(e)
|
|
1203
|
-
return
|
|
1204
1198
|
}
|
|
1205
|
-
|
|
1206
|
-
// 5. Read all bytes from reader, given successSteps and errorSteps.
|
|
1207
|
-
readAllBytes(reader, successSteps, errorSteps)
|
|
1208
1199
|
}
|
|
1209
1200
|
|
|
1210
1201
|
/**
|
|
@@ -1241,15 +1232,16 @@ function isomorphicEncode (input) {
|
|
|
1241
1232
|
/**
|
|
1242
1233
|
* @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
|
|
1243
1234
|
* @see https://streams.spec.whatwg.org/#read-loop
|
|
1244
|
-
* @param {
|
|
1235
|
+
* @param {ReadableStream<Uint8Array<ArrayBuffer>>} reader
|
|
1245
1236
|
* @param {(bytes: Uint8Array) => void} successSteps
|
|
1246
1237
|
* @param {(error: Error) => void} failureSteps
|
|
1238
|
+
* @returns {Promise<void>}
|
|
1247
1239
|
*/
|
|
1248
1240
|
async function readAllBytes (reader, successSteps, failureSteps) {
|
|
1249
|
-
const bytes = []
|
|
1250
|
-
let byteLength = 0
|
|
1251
|
-
|
|
1252
1241
|
try {
|
|
1242
|
+
const bytes = []
|
|
1243
|
+
let byteLength = 0
|
|
1244
|
+
|
|
1253
1245
|
do {
|
|
1254
1246
|
const { done, value: chunk } = await reader.read()
|
|
1255
1247
|
|
|
@@ -1262,7 +1254,7 @@ async function readAllBytes (reader, successSteps, failureSteps) {
|
|
|
1262
1254
|
// 1. If chunk is not a Uint8Array object, call failureSteps
|
|
1263
1255
|
// with a TypeError and abort these steps.
|
|
1264
1256
|
if (!isUint8Array(chunk)) {
|
|
1265
|
-
failureSteps(TypeError('Received non-Uint8Array chunk'))
|
|
1257
|
+
failureSteps(new TypeError('Received non-Uint8Array chunk'))
|
|
1266
1258
|
return
|
|
1267
1259
|
}
|
|
1268
1260
|
|
|
@@ -1324,10 +1316,17 @@ function urlIsHttpHttpsScheme (url) {
|
|
|
1324
1316
|
return protocol === 'http:' || protocol === 'https:'
|
|
1325
1317
|
}
|
|
1326
1318
|
|
|
1319
|
+
/**
|
|
1320
|
+
* @typedef {Object} RangeHeaderValue
|
|
1321
|
+
* @property {number|null} rangeStartValue
|
|
1322
|
+
* @property {number|null} rangeEndValue
|
|
1323
|
+
*/
|
|
1324
|
+
|
|
1327
1325
|
/**
|
|
1328
1326
|
* @see https://fetch.spec.whatwg.org/#simple-range-header-value
|
|
1329
1327
|
* @param {string} value
|
|
1330
1328
|
* @param {boolean} allowWhitespace
|
|
1329
|
+
* @return {RangeHeaderValue|'failure'}
|
|
1331
1330
|
*/
|
|
1332
1331
|
function simpleRangeHeaderValue (value, allowWhitespace) {
|
|
1333
1332
|
// 1. Let data be the isomorphic decoding of value.
|
|
@@ -1732,7 +1731,6 @@ module.exports = {
|
|
|
1732
1731
|
isAborted,
|
|
1733
1732
|
isCancelled,
|
|
1734
1733
|
isValidEncodedURL,
|
|
1735
|
-
createDeferredPromise,
|
|
1736
1734
|
ReadableStreamFrom,
|
|
1737
1735
|
tryUpgradeRequestToAPotentiallyTrustworthyURL,
|
|
1738
1736
|
clampAndCoarsenConnectionTimingInfo,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { types, inspect } = require('node:util')
|
|
4
4
|
const { markAsUncloneable } = require('node:worker_threads')
|
|
5
|
-
const { toUSVString } = require('../../core/util')
|
|
6
5
|
|
|
7
6
|
const UNDEFINED = 1
|
|
8
7
|
const BOOLEAN = 2
|
|
@@ -23,22 +22,48 @@ const webidl = {
|
|
|
23
22
|
is: {}
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @description Instantiate an error.
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} opts
|
|
29
|
+
* @param {string} opts.header
|
|
30
|
+
* @param {string} opts.message
|
|
31
|
+
* @returns {TypeError}
|
|
32
|
+
*/
|
|
26
33
|
webidl.errors.exception = function (message) {
|
|
27
34
|
return new TypeError(`${message.header}: ${message.message}`)
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
/**
|
|
38
|
+
* @description Instantiate an error when conversion from one type to another has failed.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} opts
|
|
41
|
+
* @param {string} opts.prefix
|
|
42
|
+
* @param {string} opts.argument
|
|
43
|
+
* @param {string[]} opts.types
|
|
44
|
+
* @returns {TypeError}
|
|
45
|
+
*/
|
|
46
|
+
webidl.errors.conversionFailed = function (opts) {
|
|
47
|
+
const plural = opts.types.length === 1 ? '' : ' one of'
|
|
32
48
|
const message =
|
|
33
|
-
`${
|
|
34
|
-
`${plural}: ${
|
|
49
|
+
`${opts.argument} could not be converted to` +
|
|
50
|
+
`${plural}: ${opts.types.join(', ')}.`
|
|
35
51
|
|
|
36
52
|
return webidl.errors.exception({
|
|
37
|
-
header:
|
|
53
|
+
header: opts.prefix,
|
|
38
54
|
message
|
|
39
55
|
})
|
|
40
56
|
}
|
|
41
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @description Instantiate an error when an invalid argument is provided
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} context
|
|
62
|
+
* @param {string} context.prefix
|
|
63
|
+
* @param {string} context.value
|
|
64
|
+
* @param {string} context.type
|
|
65
|
+
* @returns {TypeError}
|
|
66
|
+
*/
|
|
42
67
|
webidl.errors.invalidArgument = function (context) {
|
|
43
68
|
return webidl.errors.exception({
|
|
44
69
|
header: context.prefix,
|
|
@@ -278,6 +303,8 @@ webidl.util.Stringify = function (V) {
|
|
|
278
303
|
return inspect(V)
|
|
279
304
|
case STRING:
|
|
280
305
|
return `"${V}"`
|
|
306
|
+
case BIGINT:
|
|
307
|
+
return `${V}n`
|
|
281
308
|
default:
|
|
282
309
|
return `${V}`
|
|
283
310
|
}
|
|
@@ -468,6 +495,17 @@ webidl.nullableConverter = function (converter) {
|
|
|
468
495
|
}
|
|
469
496
|
}
|
|
470
497
|
|
|
498
|
+
/**
|
|
499
|
+
* @param {*} value
|
|
500
|
+
* @returns {boolean}
|
|
501
|
+
*/
|
|
502
|
+
webidl.is.USVString = function (value) {
|
|
503
|
+
return (
|
|
504
|
+
typeof value === 'string' &&
|
|
505
|
+
value.isWellFormed()
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
|
|
471
509
|
webidl.is.ReadableStream = webidl.util.MakeTypeAssertion(ReadableStream)
|
|
472
510
|
webidl.is.Blob = webidl.util.MakeTypeAssertion(Blob)
|
|
473
511
|
webidl.is.URLSearchParams = webidl.util.MakeTypeAssertion(URLSearchParams)
|
|
@@ -529,13 +567,23 @@ webidl.converters.ByteString = function (V, prefix, argument) {
|
|
|
529
567
|
return x
|
|
530
568
|
}
|
|
531
569
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
570
|
+
/**
|
|
571
|
+
* @param {unknown} value
|
|
572
|
+
* @returns {string}
|
|
573
|
+
* @see https://webidl.spec.whatwg.org/#es-USVString
|
|
574
|
+
*/
|
|
575
|
+
webidl.converters.USVString = function (value) {
|
|
576
|
+
// TODO: rewrite this so we can control the errors thrown
|
|
577
|
+
if (typeof value === 'string') {
|
|
578
|
+
return value.toWellFormed()
|
|
579
|
+
}
|
|
580
|
+
return `${value}`.toWellFormed()
|
|
581
|
+
}
|
|
535
582
|
|
|
536
583
|
// https://webidl.spec.whatwg.org/#es-boolean
|
|
537
584
|
webidl.converters.boolean = function (V) {
|
|
538
585
|
// 1. Let x be the result of computing ToBoolean(V).
|
|
586
|
+
// https://262.ecma-international.org/10.0/index.html#table-10
|
|
539
587
|
const x = Boolean(V)
|
|
540
588
|
|
|
541
589
|
// 2. Return the IDL boolean value that is the one that represents
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
|
|
4
4
|
const { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
|
|
5
|
-
const { channels } = require('../../core/diagnostics')
|
|
6
5
|
const { makeRequest } = require('../fetch/request')
|
|
7
6
|
const { fetching } = require('../fetch/index')
|
|
8
7
|
const { Headers, getHeadersList } = require('../fetch/headers')
|
|
@@ -105,7 +104,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
105
104
|
// 1. If response is a network error or its status is not 101,
|
|
106
105
|
// fail the WebSocket connection.
|
|
107
106
|
if (response.type === 'error' || response.status !== 101) {
|
|
108
|
-
failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.')
|
|
107
|
+
failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.', response.error)
|
|
109
108
|
return
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -200,14 +199,6 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
|
|
|
200
199
|
response.socket.on('close', handler.onSocketClose)
|
|
201
200
|
response.socket.on('error', handler.onSocketError)
|
|
202
201
|
|
|
203
|
-
if (channels.open.hasSubscribers) {
|
|
204
|
-
channels.open.publish({
|
|
205
|
-
address: response.socket.address(),
|
|
206
|
-
protocol: secProtocol,
|
|
207
|
-
extensions: secExtension
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
202
|
handler.wasEverConnected = true
|
|
212
203
|
handler.onConnectionEstablished(response, extensions)
|
|
213
204
|
}
|
|
@@ -298,9 +289,10 @@ function closeWebSocketConnection (object, code, reason, validate = false) {
|
|
|
298
289
|
* @param {import('./websocket').Handler} handler
|
|
299
290
|
* @param {number} code
|
|
300
291
|
* @param {string|undefined} reason
|
|
292
|
+
* @param {unknown} cause
|
|
301
293
|
* @returns {void}
|
|
302
294
|
*/
|
|
303
|
-
function failWebsocketConnection (handler, code, reason) {
|
|
295
|
+
function failWebsocketConnection (handler, code, reason, cause) {
|
|
304
296
|
// If _The WebSocket Connection is Established_ prior to the point where
|
|
305
297
|
// the endpoint is required to _Fail the WebSocket Connection_, the
|
|
306
298
|
// endpoint SHOULD send a Close frame with an appropriate status code
|
|
@@ -315,7 +307,7 @@ function failWebsocketConnection (handler, code, reason) {
|
|
|
315
307
|
handler.socket.destroy()
|
|
316
308
|
}
|
|
317
309
|
|
|
318
|
-
handler.onFail(code, reason)
|
|
310
|
+
handler.onFail(code, reason, cause)
|
|
319
311
|
}
|
|
320
312
|
|
|
321
313
|
module.exports = {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const { Writable } = require('node:stream')
|
|
4
4
|
const assert = require('node:assert')
|
|
5
5
|
const { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require('./constants')
|
|
6
|
-
const { channels } = require('../../core/diagnostics')
|
|
7
6
|
const {
|
|
8
7
|
isValidStatusCode,
|
|
9
8
|
isValidOpcode,
|
|
@@ -423,22 +422,13 @@ class ByteParser extends Writable {
|
|
|
423
422
|
|
|
424
423
|
this.#handler.socket.write(frame.createFrame(opcodes.PONG))
|
|
425
424
|
|
|
426
|
-
|
|
427
|
-
channels.ping.publish({
|
|
428
|
-
payload: body
|
|
429
|
-
})
|
|
430
|
-
}
|
|
425
|
+
this.#handler.onPing(body)
|
|
431
426
|
}
|
|
432
427
|
} else if (opcode === opcodes.PONG) {
|
|
433
428
|
// A Pong frame MAY be sent unsolicited. This serves as a
|
|
434
429
|
// unidirectional heartbeat. A response to an unsolicited Pong frame is
|
|
435
430
|
// not expected.
|
|
436
|
-
|
|
437
|
-
if (channels.pong.hasSubscribers) {
|
|
438
|
-
channels.pong.publish({
|
|
439
|
-
payload: body
|
|
440
|
-
})
|
|
441
|
-
}
|
|
431
|
+
this.#handler.onPong(body)
|
|
442
432
|
}
|
|
443
433
|
|
|
444
434
|
return true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { webidl } = require('../../
|
|
3
|
+
const { webidl } = require('../../webidl')
|
|
4
4
|
const { validateCloseCodeAndReason } = require('../util')
|
|
5
5
|
const { kConstruct } = require('../../../core/symbols')
|
|
6
6
|
const { kEnumerableProperty } = require('../../../core/util')
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { createDeferredPromise
|
|
3
|
+
const { createDeferredPromise } = require('../../../util/promise')
|
|
4
|
+
const { environmentSettingsObject } = require('../../fetch/util')
|
|
4
5
|
const { states, opcodes, sentCloseFrameState } = require('../constants')
|
|
5
|
-
const { webidl } = require('../../
|
|
6
|
+
const { webidl } = require('../../webidl')
|
|
6
7
|
const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util')
|
|
7
8
|
const { establishWebSocketConnection, failWebsocketConnection, closeWebSocketConnection } = require('../connection')
|
|
8
9
|
const { types } = require('node:util')
|
|
@@ -21,11 +22,11 @@ class WebSocketStream {
|
|
|
21
22
|
#url
|
|
22
23
|
|
|
23
24
|
// Each WebSocketStream object has an associated opened promise , which is a promise.
|
|
24
|
-
/** @type {
|
|
25
|
+
/** @type {import('../../../util/promise').DeferredPromise} */
|
|
25
26
|
#openedPromise
|
|
26
27
|
|
|
27
28
|
// Each WebSocketStream object has an associated closed promise , which is a promise.
|
|
28
|
-
/** @type {
|
|
29
|
+
/** @type {import('../../../util/promise').DeferredPromise} */
|
|
29
30
|
#closedPromise
|
|
30
31
|
|
|
31
32
|
// Each WebSocketStream object has an associated readable stream , which is a ReadableStream .
|
|
@@ -64,6 +65,8 @@ class WebSocketStream {
|
|
|
64
65
|
this.#handler.socket.destroy()
|
|
65
66
|
},
|
|
66
67
|
onSocketClose: () => this.#onSocketClose(),
|
|
68
|
+
onPing: () => {},
|
|
69
|
+
onPong: () => {},
|
|
67
70
|
|
|
68
71
|
readyState: states.CONNECTING,
|
|
69
72
|
socket: null,
|
|
@@ -388,7 +391,7 @@ class WebSocketStream {
|
|
|
388
391
|
// 6. If the connection was closed cleanly ,
|
|
389
392
|
if (wasClean) {
|
|
390
393
|
// 6.1. Close stream ’s readable stream .
|
|
391
|
-
this.#
|
|
394
|
+
this.#readableStreamController.close()
|
|
392
395
|
|
|
393
396
|
// 6.2. Error stream ’s writable stream with an " InvalidStateError " DOMException indicating that a closed WebSocketStream cannot be written to.
|
|
394
397
|
if (!this.#writableStream.locked) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { webidl } = require('../
|
|
3
|
+
const { webidl } = require('../webidl')
|
|
4
4
|
const { URLSerializer } = require('../fetch/data-url')
|
|
5
5
|
const { environmentSettingsObject } = require('../fetch/util')
|
|
6
6
|
const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants')
|
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
isConnecting,
|
|
9
9
|
isEstablished,
|
|
10
10
|
isClosing,
|
|
11
|
+
isClosed,
|
|
11
12
|
isValidSubprotocol,
|
|
12
13
|
fireEvent,
|
|
13
14
|
utf8Decode,
|
|
@@ -21,6 +22,7 @@ const { getGlobalDispatcher } = require('../../global')
|
|
|
21
22
|
const { types } = require('node:util')
|
|
22
23
|
const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
|
|
23
24
|
const { SendQueue } = require('./sender')
|
|
25
|
+
const { WebsocketFrameSend } = require('./frame')
|
|
24
26
|
const { channels } = require('../../core/diagnostics')
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -33,6 +35,8 @@ const { channels } = require('../../core/diagnostics')
|
|
|
33
35
|
* @property {(chunk: Buffer) => void} onSocketData
|
|
34
36
|
* @property {(err: Error) => void} onSocketError
|
|
35
37
|
* @property {() => void} onSocketClose
|
|
38
|
+
* @property {(body: Buffer) => void} onPing
|
|
39
|
+
* @property {(body: Buffer) => void} onPong
|
|
36
40
|
*
|
|
37
41
|
* @property {number} readyState
|
|
38
42
|
* @property {import('stream').Duplex} socket
|
|
@@ -60,7 +64,7 @@ class WebSocket extends EventTarget {
|
|
|
60
64
|
/** @type {Handler} */
|
|
61
65
|
#handler = {
|
|
62
66
|
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
|
|
63
|
-
onFail: (code, reason) => this.#onFail(code, reason),
|
|
67
|
+
onFail: (code, reason, cause) => this.#onFail(code, reason, cause),
|
|
64
68
|
onMessage: (opcode, data) => this.#onMessage(opcode, data),
|
|
65
69
|
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
|
|
66
70
|
onParserDrain: () => this.#onParserDrain(),
|
|
@@ -79,6 +83,22 @@ class WebSocket extends EventTarget {
|
|
|
79
83
|
this.#handler.socket.destroy()
|
|
80
84
|
},
|
|
81
85
|
onSocketClose: () => this.#onSocketClose(),
|
|
86
|
+
onPing: (body) => {
|
|
87
|
+
if (channels.ping.hasSubscribers) {
|
|
88
|
+
channels.ping.publish({
|
|
89
|
+
payload: body,
|
|
90
|
+
websocket: this
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onPong: (body) => {
|
|
95
|
+
if (channels.pong.hasSubscribers) {
|
|
96
|
+
channels.pong.publish({
|
|
97
|
+
payload: body,
|
|
98
|
+
websocket: this
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
},
|
|
82
102
|
|
|
83
103
|
readyState: states.CONNECTING,
|
|
84
104
|
socket: null,
|
|
@@ -460,13 +480,22 @@ class WebSocket extends EventTarget {
|
|
|
460
480
|
|
|
461
481
|
// 4. Fire an event named open at the WebSocket object.
|
|
462
482
|
fireEvent('open', this)
|
|
483
|
+
|
|
484
|
+
if (channels.open.hasSubscribers) {
|
|
485
|
+
channels.open.publish({
|
|
486
|
+
address: response.socket.address(),
|
|
487
|
+
protocol: this.#protocol,
|
|
488
|
+
extensions: this.#extensions,
|
|
489
|
+
websocket: this
|
|
490
|
+
})
|
|
491
|
+
}
|
|
463
492
|
}
|
|
464
493
|
|
|
465
|
-
#onFail (code, reason) {
|
|
494
|
+
#onFail (code, reason, cause) {
|
|
466
495
|
if (reason) {
|
|
467
496
|
// TODO: process.nextTick
|
|
468
497
|
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
|
|
469
|
-
error: new Error(reason),
|
|
498
|
+
error: new Error(reason, cause ? { cause } : undefined),
|
|
470
499
|
message: reason
|
|
471
500
|
})
|
|
472
501
|
}
|
|
@@ -586,8 +615,34 @@ class WebSocket extends EventTarget {
|
|
|
586
615
|
})
|
|
587
616
|
}
|
|
588
617
|
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* @param {WebSocket} ws
|
|
621
|
+
* @param {Buffer|undefined} buffer
|
|
622
|
+
*/
|
|
623
|
+
static ping (ws, buffer) {
|
|
624
|
+
if (Buffer.isBuffer(buffer)) {
|
|
625
|
+
if (buffer.length > 125) {
|
|
626
|
+
throw new TypeError('A PING frame cannot have a body larger than 125 bytes.')
|
|
627
|
+
}
|
|
628
|
+
} else if (buffer !== undefined) {
|
|
629
|
+
throw new TypeError('Expected buffer payload')
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// An endpoint MAY send a Ping frame any time after the connection is
|
|
633
|
+
// established and before the connection is closed.
|
|
634
|
+
const readyState = ws.#handler.readyState
|
|
635
|
+
|
|
636
|
+
if (isEstablished(readyState) && !isClosing(readyState) && !isClosed(readyState)) {
|
|
637
|
+
const frame = new WebsocketFrameSend(buffer)
|
|
638
|
+
ws.#handler.socket.write(frame.createFrame(opcodes.PING))
|
|
639
|
+
}
|
|
640
|
+
}
|
|
589
641
|
}
|
|
590
642
|
|
|
643
|
+
const { ping } = WebSocket
|
|
644
|
+
Reflect.deleteProperty(WebSocket, 'ping')
|
|
645
|
+
|
|
591
646
|
// https://websockets.spec.whatwg.org/#dom-websocket-connecting
|
|
592
647
|
WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING
|
|
593
648
|
// https://websockets.spec.whatwg.org/#dom-websocket-open
|
|
@@ -682,5 +737,6 @@ webidl.converters.WebSocketSendData = function (V) {
|
|
|
682
737
|
}
|
|
683
738
|
|
|
684
739
|
module.exports = {
|
|
685
|
-
WebSocket
|
|
740
|
+
WebSocket,
|
|
741
|
+
ping
|
|
686
742
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.12.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": {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w",
|
|
92
92
|
"test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types",
|
|
93
93
|
"test:webidl": "borp -p \"test/webidl/*.js\"",
|
|
94
|
-
"test:websocket": "borp -p \"test/websocket
|
|
94
|
+
"test:websocket": "borp -p \"test/websocket/**/*.js\"",
|
|
95
95
|
"test:websocket:autobahn": "node test/autobahn/client.js",
|
|
96
96
|
"test:websocket:autobahn:report": "node test/autobahn/report.js",
|
|
97
97
|
"test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
|
|
@@ -112,20 +112,20 @@
|
|
|
112
112
|
"@sinonjs/fake-timers": "^12.0.0",
|
|
113
113
|
"@types/node": "^18.19.50",
|
|
114
114
|
"abort-controller": "^3.0.0",
|
|
115
|
-
"borp": "^0.
|
|
115
|
+
"borp": "^0.20.0",
|
|
116
116
|
"c8": "^10.0.0",
|
|
117
117
|
"cross-env": "^7.0.3",
|
|
118
118
|
"dns-packet": "^5.4.0",
|
|
119
119
|
"esbuild": "^0.25.2",
|
|
120
120
|
"eslint": "^9.9.0",
|
|
121
|
-
"fast-check": "^
|
|
121
|
+
"fast-check": "^4.1.1",
|
|
122
122
|
"https-pem": "^3.0.0",
|
|
123
123
|
"husky": "^9.0.7",
|
|
124
124
|
"jest": "^29.0.2",
|
|
125
125
|
"neostandard": "^0.12.0",
|
|
126
126
|
"node-forge": "^1.3.1",
|
|
127
127
|
"proxy": "^2.1.1",
|
|
128
|
-
"tsd": "^0.
|
|
128
|
+
"tsd": "^0.32.0",
|
|
129
129
|
"typescript": "^5.6.2",
|
|
130
130
|
"ws": "^8.11.0"
|
|
131
131
|
},
|
|
@@ -31,6 +31,15 @@ declare namespace DiagnosticsChannel {
|
|
|
31
31
|
export interface RequestBodySentMessage {
|
|
32
32
|
request: Request;
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
export interface RequestBodyChunkSentMessage {
|
|
36
|
+
request: Request;
|
|
37
|
+
chunk: Uint8Array | string;
|
|
38
|
+
}
|
|
39
|
+
export interface RequestBodyChunkReceivedMessage {
|
|
40
|
+
request: Request;
|
|
41
|
+
chunk: Buffer;
|
|
42
|
+
}
|
|
34
43
|
export interface RequestHeadersMessage {
|
|
35
44
|
request: Request;
|
|
36
45
|
response: Response;
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -97,7 +97,8 @@ declare class Dispatcher extends EventEmitter {
|
|
|
97
97
|
|
|
98
98
|
declare namespace Dispatcher {
|
|
99
99
|
export interface ComposedDispatcher extends Dispatcher {}
|
|
100
|
-
export type
|
|
100
|
+
export type Dispatch = Dispatcher['dispatch']
|
|
101
|
+
export type DispatcherComposeInterceptor = (dispatch: Dispatch) => Dispatch
|
|
101
102
|
export interface DispatchOptions {
|
|
102
103
|
origin?: string | URL;
|
|
103
104
|
path: string;
|
|
@@ -276,6 +277,6 @@ declare namespace Dispatcher {
|
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
export interface DispatchInterceptor {
|
|
279
|
-
(dispatch:
|
|
280
|
+
(dispatch: Dispatch): Dispatch
|
|
280
281
|
}
|
|
281
282
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Agent from './agent'
|
|
2
|
+
import ProxyAgent from './proxy-agent'
|
|
2
3
|
import Dispatcher from './dispatcher'
|
|
3
4
|
|
|
4
5
|
export default EnvHttpProxyAgent
|
|
@@ -10,7 +11,7 @@ declare class EnvHttpProxyAgent extends Dispatcher {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
declare namespace EnvHttpProxyAgent {
|
|
13
|
-
export interface Options extends
|
|
14
|
+
export interface Options extends Omit<ProxyAgent.Options, 'uri'> {
|
|
14
15
|
/** Overrides the value of the HTTP_PROXY environment variable */
|
|
15
16
|
httpProxy?: string;
|
|
16
17
|
/** Overrides the value of the HTTPS_PROXY environment variable */
|
package/types/eventsource.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ interface EventSource extends EventTarget {
|
|
|
18
18
|
readonly CLOSED: 2
|
|
19
19
|
readonly CONNECTING: 0
|
|
20
20
|
readonly OPEN: 1
|
|
21
|
-
onerror: (this: EventSource, ev: ErrorEvent) => any
|
|
22
|
-
onmessage: (this: EventSource, ev: MessageEvent) => any
|
|
23
|
-
onopen: (this: EventSource, ev: Event) => any
|
|
21
|
+
onerror: ((this: EventSource, ev: ErrorEvent) => any) | null
|
|
22
|
+
onmessage: ((this: EventSource, ev: MessageEvent) => any) | null
|
|
23
|
+
onopen: ((this: EventSource, ev: Event) => any) | null
|
|
24
24
|
readonly readyState: 0 | 1 | 2
|
|
25
25
|
readonly url: string
|
|
26
26
|
readonly withCredentials: boolean
|
package/types/fetch.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export class BodyMixin {
|
|
|
33
33
|
|
|
34
34
|
readonly arrayBuffer: () => Promise<ArrayBuffer>
|
|
35
35
|
readonly blob: () => Promise<Blob>
|
|
36
|
+
readonly bytes: () => Promise<Uint8Array>
|
|
36
37
|
/**
|
|
37
38
|
* @deprecated This method is not recommended for parsing multipart/form-data bodies in server environments.
|
|
38
39
|
* It is recommended to use a library such as [@fastify/busboy](https://www.npmjs.com/package/@fastify/busboy) as follows:
|