undici 6.19.8 → 7.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -9
- package/docs/docs/api/Agent.md +0 -3
- package/docs/docs/api/Client.md +0 -2
- package/docs/docs/api/Dispatcher.md +204 -6
- package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
- package/docs/docs/api/Fetch.md +1 -0
- package/docs/docs/api/Pool.md +0 -1
- package/docs/docs/api/RetryHandler.md +1 -1
- package/index.js +0 -4
- package/lib/api/api-connect.js +3 -1
- package/lib/api/api-pipeline.js +3 -4
- package/lib/api/api-request.js +29 -46
- package/lib/api/api-stream.js +36 -49
- package/lib/api/api-upgrade.js +5 -3
- package/lib/api/readable.js +71 -27
- package/lib/core/connect.js +39 -24
- package/lib/core/errors.js +17 -4
- package/lib/core/request.js +7 -5
- package/lib/core/symbols.js +0 -1
- package/lib/core/tree.js +6 -0
- package/lib/core/util.js +1 -11
- package/lib/dispatcher/agent.js +3 -17
- package/lib/dispatcher/balanced-pool.js +5 -8
- package/lib/dispatcher/client-h1.js +44 -39
- package/lib/dispatcher/client.js +3 -27
- package/lib/dispatcher/dispatcher-base.js +2 -34
- package/lib/dispatcher/dispatcher.js +3 -24
- package/lib/dispatcher/pool.js +3 -6
- package/lib/dispatcher/proxy-agent.js +3 -6
- package/lib/handler/decorator-handler.js +24 -0
- package/lib/handler/redirect-handler.js +9 -0
- package/lib/handler/retry-handler.js +22 -3
- package/lib/interceptor/dump.js +2 -2
- package/lib/interceptor/redirect.js +11 -14
- package/lib/interceptor/response-error.js +89 -0
- package/lib/llhttp/constants.d.ts +97 -0
- package/lib/llhttp/constants.js +412 -192
- package/lib/llhttp/constants.js.map +1 -0
- package/lib/llhttp/llhttp-wasm.js +11 -1
- package/lib/llhttp/llhttp_simd-wasm.js +11 -1
- package/lib/llhttp/utils.d.ts +2 -0
- package/lib/llhttp/utils.js +9 -9
- package/lib/llhttp/utils.js.map +1 -0
- package/lib/mock/mock-client.js +2 -2
- package/lib/mock/mock-pool.js +2 -2
- package/lib/mock/mock-symbols.js +1 -0
- package/lib/util/timers.js +324 -44
- package/lib/web/cookies/index.js +15 -13
- package/lib/web/cookies/parse.js +2 -2
- package/lib/web/eventsource/eventsource-stream.js +9 -8
- package/lib/web/eventsource/eventsource.js +10 -6
- package/lib/web/fetch/body.js +4 -6
- package/lib/web/fetch/data-url.js +1 -1
- package/lib/web/fetch/formdata-parser.js +1 -2
- package/lib/web/fetch/formdata.js +28 -37
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +7 -8
- package/lib/web/fetch/request.js +7 -24
- package/lib/web/fetch/response.js +9 -22
- package/lib/web/fetch/symbols.js +0 -1
- package/lib/web/fetch/util.js +3 -12
- package/lib/web/fetch/webidl.js +73 -62
- package/lib/web/websocket/connection.js +26 -174
- package/lib/web/websocket/constants.js +1 -1
- package/lib/web/websocket/frame.js +45 -3
- package/lib/web/websocket/receiver.js +28 -26
- package/lib/web/websocket/sender.js +18 -13
- package/lib/web/websocket/util.js +20 -74
- package/lib/web/websocket/websocket.js +294 -70
- package/package.json +16 -29
- package/scripts/strip-comments.js +3 -1
- package/types/agent.d.ts +7 -7
- package/types/api.d.ts +24 -24
- package/types/balanced-pool.d.ts +11 -11
- package/types/client.d.ts +11 -12
- package/types/diagnostics-channel.d.ts +10 -10
- package/types/dispatcher.d.ts +96 -97
- package/types/env-http-proxy-agent.d.ts +2 -2
- package/types/errors.d.ts +53 -47
- package/types/eventsource.d.ts +0 -2
- package/types/fetch.d.ts +8 -8
- package/types/formdata.d.ts +7 -7
- package/types/global-dispatcher.d.ts +4 -4
- package/types/global-origin.d.ts +5 -5
- package/types/handlers.d.ts +4 -4
- package/types/header.d.ts +157 -1
- package/types/index.d.ts +42 -46
- package/types/interceptors.d.ts +10 -8
- package/types/mock-agent.d.ts +18 -18
- package/types/mock-client.d.ts +4 -4
- package/types/mock-errors.d.ts +3 -3
- package/types/mock-interceptor.d.ts +19 -19
- package/types/mock-pool.d.ts +4 -4
- package/types/patch.d.ts +0 -42
- package/types/pool-stats.d.ts +8 -8
- package/types/pool.d.ts +12 -12
- package/types/proxy-agent.d.ts +4 -4
- package/types/readable.d.ts +14 -9
- package/types/retry-agent.d.ts +1 -1
- package/types/retry-handler.d.ts +8 -8
- package/types/util.d.ts +3 -3
- package/types/utility.d.ts +7 -0
- package/types/webidl.d.ts +22 -4
- package/types/websocket.d.ts +1 -3
- package/docs/docs/api/DispatchInterceptor.md +0 -60
- package/lib/interceptor/redirect-interceptor.js +0 -21
- package/lib/web/fetch/file.js +0 -126
- package/lib/web/fileapi/encoding.js +0 -290
- package/lib/web/fileapi/filereader.js +0 -344
- package/lib/web/fileapi/progressevent.js +0 -78
- package/lib/web/fileapi/symbols.js +0 -10
- package/lib/web/fileapi/util.js +0 -391
- package/lib/web/websocket/symbols.js +0 -12
- package/types/file.d.ts +0 -39
- package/types/filereader.d.ts +0 -54
package/lib/web/fetch/webidl.js
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
const { types, inspect } = require('node:util')
|
|
4
4
|
const { toUSVString } = require('../../core/util')
|
|
5
5
|
|
|
6
|
+
const UNDEFINED = 1
|
|
7
|
+
const BOOLEAN = 2
|
|
8
|
+
const STRING = 3
|
|
9
|
+
const SYMBOL = 4
|
|
10
|
+
const NUMBER = 5
|
|
11
|
+
const BIGINT = 6
|
|
12
|
+
const NULL = 7
|
|
13
|
+
const OBJECT = 8 // function and object
|
|
14
|
+
|
|
6
15
|
/** @type {import('../../../types/webidl').Webidl} */
|
|
7
16
|
const webidl = {}
|
|
8
17
|
webidl.converters = {}
|
|
@@ -33,19 +42,19 @@ webidl.errors.invalidArgument = function (context) {
|
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
// https://webidl.spec.whatwg.org/#implements
|
|
36
|
-
webidl.brandCheck = function (V, I
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
webidl.brandCheck = function (V, I) {
|
|
46
|
+
if (!(V instanceof I)) {
|
|
47
|
+
const err = new TypeError('Illegal invocation')
|
|
48
|
+
err.code = 'ERR_INVALID_THIS' // node compat.
|
|
49
|
+
throw err
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
webidl.brandCheckMultiple = function (V, List) {
|
|
54
|
+
if (List.every((clazz) => !(V instanceof clazz))) {
|
|
55
|
+
const err = new TypeError('Illegal invocation')
|
|
56
|
+
err.code = 'ERR_INVALID_THIS' // node compat.
|
|
57
|
+
throw err
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
|
|
@@ -69,23 +78,47 @@ webidl.illegalConstructor = function () {
|
|
|
69
78
|
// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
|
|
70
79
|
webidl.util.Type = function (V) {
|
|
71
80
|
switch (typeof V) {
|
|
72
|
-
case 'undefined': return
|
|
73
|
-
case 'boolean': return
|
|
74
|
-
case 'string': return
|
|
75
|
-
case 'symbol': return
|
|
76
|
-
case 'number': return
|
|
77
|
-
case 'bigint': return
|
|
81
|
+
case 'undefined': return UNDEFINED
|
|
82
|
+
case 'boolean': return BOOLEAN
|
|
83
|
+
case 'string': return STRING
|
|
84
|
+
case 'symbol': return SYMBOL
|
|
85
|
+
case 'number': return NUMBER
|
|
86
|
+
case 'bigint': return BIGINT
|
|
78
87
|
case 'function':
|
|
79
88
|
case 'object': {
|
|
80
89
|
if (V === null) {
|
|
81
|
-
return
|
|
90
|
+
return NULL
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
return
|
|
93
|
+
return OBJECT
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
webidl.util.Types = {
|
|
99
|
+
UNDEFINED,
|
|
100
|
+
BOOLEAN,
|
|
101
|
+
STRING,
|
|
102
|
+
SYMBOL,
|
|
103
|
+
NUMBER,
|
|
104
|
+
BIGINT,
|
|
105
|
+
NULL,
|
|
106
|
+
OBJECT
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
webidl.util.TypeValueToString = function (o) {
|
|
110
|
+
switch (webidl.util.Type(o)) {
|
|
111
|
+
case UNDEFINED: return 'Undefined'
|
|
112
|
+
case BOOLEAN: return 'Boolean'
|
|
113
|
+
case STRING: return 'String'
|
|
114
|
+
case SYMBOL: return 'Symbol'
|
|
115
|
+
case NUMBER: return 'Number'
|
|
116
|
+
case BIGINT: return 'BigInt'
|
|
117
|
+
case NULL: return 'Null'
|
|
118
|
+
case OBJECT: return 'Object'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
89
122
|
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
|
|
90
123
|
webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
|
|
91
124
|
let upperBound
|
|
@@ -224,11 +257,11 @@ webidl.util.Stringify = function (V) {
|
|
|
224
257
|
const type = webidl.util.Type(V)
|
|
225
258
|
|
|
226
259
|
switch (type) {
|
|
227
|
-
case
|
|
260
|
+
case SYMBOL:
|
|
228
261
|
return `Symbol(${V.description})`
|
|
229
|
-
case
|
|
262
|
+
case OBJECT:
|
|
230
263
|
return inspect(V)
|
|
231
|
-
case
|
|
264
|
+
case STRING:
|
|
232
265
|
return `"${V}"`
|
|
233
266
|
default:
|
|
234
267
|
return `${V}`
|
|
@@ -239,7 +272,7 @@ webidl.util.Stringify = function (V) {
|
|
|
239
272
|
webidl.sequenceConverter = function (converter) {
|
|
240
273
|
return (V, prefix, argument, Iterable) => {
|
|
241
274
|
// 1. If Type(V) is not Object, throw a TypeError.
|
|
242
|
-
if (webidl.util.Type(V) !==
|
|
275
|
+
if (webidl.util.Type(V) !== OBJECT) {
|
|
243
276
|
throw webidl.errors.exception({
|
|
244
277
|
header: prefix,
|
|
245
278
|
message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.`
|
|
@@ -282,10 +315,10 @@ webidl.sequenceConverter = function (converter) {
|
|
|
282
315
|
webidl.recordConverter = function (keyConverter, valueConverter) {
|
|
283
316
|
return (O, prefix, argument) => {
|
|
284
317
|
// 1. If Type(O) is not Object, throw a TypeError.
|
|
285
|
-
if (webidl.util.Type(O) !==
|
|
318
|
+
if (webidl.util.Type(O) !== OBJECT) {
|
|
286
319
|
throw webidl.errors.exception({
|
|
287
320
|
header: prefix,
|
|
288
|
-
message: `${argument} ("${webidl.util.
|
|
321
|
+
message: `${argument} ("${webidl.util.TypeValueToString(O)}") is not an Object.`
|
|
289
322
|
})
|
|
290
323
|
}
|
|
291
324
|
|
|
@@ -340,8 +373,8 @@ webidl.recordConverter = function (keyConverter, valueConverter) {
|
|
|
340
373
|
}
|
|
341
374
|
|
|
342
375
|
webidl.interfaceConverter = function (i) {
|
|
343
|
-
return (V, prefix, argument
|
|
344
|
-
if (
|
|
376
|
+
return (V, prefix, argument) => {
|
|
377
|
+
if (!(V instanceof i)) {
|
|
345
378
|
throw webidl.errors.exception({
|
|
346
379
|
header: prefix,
|
|
347
380
|
message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${i.name}.`
|
|
@@ -354,12 +387,9 @@ webidl.interfaceConverter = function (i) {
|
|
|
354
387
|
|
|
355
388
|
webidl.dictionaryConverter = function (converters) {
|
|
356
389
|
return (dictionary, prefix, argument) => {
|
|
357
|
-
const type = webidl.util.Type(dictionary)
|
|
358
390
|
const dict = {}
|
|
359
391
|
|
|
360
|
-
if (
|
|
361
|
-
return dict
|
|
362
|
-
} else if (type !== 'Object') {
|
|
392
|
+
if (dictionary != null && webidl.util.Type(dictionary) !== OBJECT) {
|
|
363
393
|
throw webidl.errors.exception({
|
|
364
394
|
header: prefix,
|
|
365
395
|
message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`
|
|
@@ -370,7 +400,7 @@ webidl.dictionaryConverter = function (converters) {
|
|
|
370
400
|
const { key, defaultValue, required, converter } = options
|
|
371
401
|
|
|
372
402
|
if (required === true) {
|
|
373
|
-
if (!Object.hasOwn(dictionary, key)) {
|
|
403
|
+
if (dictionary == null || !Object.hasOwn(dictionary, key)) {
|
|
374
404
|
throw webidl.errors.exception({
|
|
375
405
|
header: prefix,
|
|
376
406
|
message: `Missing required key "${key}".`
|
|
@@ -378,13 +408,13 @@ webidl.dictionaryConverter = function (converters) {
|
|
|
378
408
|
}
|
|
379
409
|
}
|
|
380
410
|
|
|
381
|
-
let value = dictionary[key]
|
|
382
|
-
const hasDefault =
|
|
411
|
+
let value = dictionary?.[key]
|
|
412
|
+
const hasDefault = defaultValue !== undefined
|
|
383
413
|
|
|
384
414
|
// Only use defaultValue if value is undefined and
|
|
385
415
|
// a defaultValue options was provided.
|
|
386
|
-
if (hasDefault && value
|
|
387
|
-
value
|
|
416
|
+
if (hasDefault && value === undefined) {
|
|
417
|
+
value = defaultValue()
|
|
388
418
|
}
|
|
389
419
|
|
|
390
420
|
// A key can be optional and have no default value.
|
|
@@ -535,7 +565,7 @@ webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
|
|
|
535
565
|
// see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances
|
|
536
566
|
// see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances
|
|
537
567
|
if (
|
|
538
|
-
webidl.util.Type(V) !==
|
|
568
|
+
webidl.util.Type(V) !== OBJECT ||
|
|
539
569
|
!types.isAnyArrayBuffer(V)
|
|
540
570
|
) {
|
|
541
571
|
throw webidl.errors.conversionFailed({
|
|
@@ -579,7 +609,7 @@ webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
|
|
579
609
|
// [[TypedArrayName]] internal slot with a value
|
|
580
610
|
// equal to T’s name, then throw a TypeError.
|
|
581
611
|
if (
|
|
582
|
-
webidl.util.Type(V) !==
|
|
612
|
+
webidl.util.Type(V) !== OBJECT ||
|
|
583
613
|
!types.isTypedArray(V) ||
|
|
584
614
|
V.constructor.name !== T.name
|
|
585
615
|
) {
|
|
@@ -620,7 +650,7 @@ webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
|
|
|
620
650
|
webidl.converters.DataView = function (V, prefix, name, opts) {
|
|
621
651
|
// 1. If Type(V) is not Object, or V does not have a
|
|
622
652
|
// [[DataView]] internal slot, then throw a TypeError.
|
|
623
|
-
if (webidl.util.Type(V) !==
|
|
653
|
+
if (webidl.util.Type(V) !== OBJECT || !types.isDataView(V)) {
|
|
624
654
|
throw webidl.errors.exception({
|
|
625
655
|
header: prefix,
|
|
626
656
|
message: `${name} is not a DataView.`
|
|
@@ -654,27 +684,6 @@ webidl.converters.DataView = function (V, prefix, name, opts) {
|
|
|
654
684
|
return V
|
|
655
685
|
}
|
|
656
686
|
|
|
657
|
-
// https://webidl.spec.whatwg.org/#BufferSource
|
|
658
|
-
webidl.converters.BufferSource = function (V, prefix, name, opts) {
|
|
659
|
-
if (types.isAnyArrayBuffer(V)) {
|
|
660
|
-
return webidl.converters.ArrayBuffer(V, prefix, name, { ...opts, allowShared: false })
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
if (types.isTypedArray(V)) {
|
|
664
|
-
return webidl.converters.TypedArray(V, V.constructor, prefix, name, { ...opts, allowShared: false })
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (types.isDataView(V)) {
|
|
668
|
-
return webidl.converters.DataView(V, prefix, name, { ...opts, allowShared: false })
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
throw webidl.errors.conversionFailed({
|
|
672
|
-
prefix,
|
|
673
|
-
argument: `${name} ("${webidl.util.Stringify(V)}")`,
|
|
674
|
-
types: ['BufferSource']
|
|
675
|
-
})
|
|
676
|
-
}
|
|
677
|
-
|
|
678
687
|
webidl.converters['sequence<ByteString>'] = webidl.sequenceConverter(
|
|
679
688
|
webidl.converters.ByteString
|
|
680
689
|
)
|
|
@@ -688,6 +697,8 @@ webidl.converters['record<ByteString, ByteString>'] = webidl.recordConverter(
|
|
|
688
697
|
webidl.converters.ByteString
|
|
689
698
|
)
|
|
690
699
|
|
|
700
|
+
webidl.converters.Blob = webidl.interfaceConverter(Blob)
|
|
701
|
+
|
|
691
702
|
module.exports = {
|
|
692
703
|
webidl
|
|
693
704
|
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { uid
|
|
4
|
-
const {
|
|
5
|
-
kReadyState,
|
|
6
|
-
kSentClose,
|
|
7
|
-
kByteParser,
|
|
8
|
-
kReceivedClose,
|
|
9
|
-
kResponse
|
|
10
|
-
} = require('./symbols')
|
|
11
|
-
const { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = require('./util')
|
|
3
|
+
const { uid } = require('./constants')
|
|
4
|
+
const { failWebsocketConnection, parseExtensions } = require('./util')
|
|
12
5
|
const { channels } = require('../../core/diagnostics')
|
|
13
|
-
const { CloseEvent } = require('./events')
|
|
14
6
|
const { makeRequest } = require('../fetch/request')
|
|
15
7
|
const { fetching } = require('../fetch/index')
|
|
16
8
|
const { Headers, getHeadersList } = require('../fetch/headers')
|
|
17
9
|
const { getDecodeSplit } = require('../fetch/util')
|
|
18
|
-
const { WebsocketFrameSend } = require('./frame')
|
|
19
10
|
|
|
20
11
|
/** @type {import('crypto')} */
|
|
21
12
|
let crypto
|
|
@@ -30,11 +21,10 @@ try {
|
|
|
30
21
|
* @see https://websockets.spec.whatwg.org/#concept-websocket-establish
|
|
31
22
|
* @param {URL} url
|
|
32
23
|
* @param {string|string[]} protocols
|
|
33
|
-
* @param {import('./websocket').
|
|
34
|
-
* @param {(
|
|
35
|
-
* @param {Partial<import('../../types/websocket').WebSocketInit>} options
|
|
24
|
+
* @param {import('./websocket').Handler} handler
|
|
25
|
+
* @param {Partial<import('../../../types/websocket').WebSocketInit>} options
|
|
36
26
|
*/
|
|
37
|
-
function establishWebSocketConnection (url, protocols, client,
|
|
27
|
+
function establishWebSocketConnection (url, protocols, client, handler, options) {
|
|
38
28
|
// 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s
|
|
39
29
|
// scheme is "ws", and to "https" otherwise.
|
|
40
30
|
const requestURL = url
|
|
@@ -75,17 +65,17 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
75
65
|
|
|
76
66
|
// 6. Append (`Sec-WebSocket-Key`, keyValue) to request’s
|
|
77
67
|
// header list.
|
|
78
|
-
request.headersList.append('sec-websocket-key', keyValue)
|
|
68
|
+
request.headersList.append('sec-websocket-key', keyValue, true)
|
|
79
69
|
|
|
80
70
|
// 7. Append (`Sec-WebSocket-Version`, `13`) to request’s
|
|
81
71
|
// header list.
|
|
82
|
-
request.headersList.append('sec-websocket-version', '13')
|
|
72
|
+
request.headersList.append('sec-websocket-version', '13', true)
|
|
83
73
|
|
|
84
74
|
// 8. For each protocol in protocols, combine
|
|
85
75
|
// (`Sec-WebSocket-Protocol`, protocol) in request’s header
|
|
86
76
|
// list.
|
|
87
77
|
for (const protocol of protocols) {
|
|
88
|
-
request.headersList.append('sec-websocket-protocol', protocol)
|
|
78
|
+
request.headersList.append('sec-websocket-protocol', protocol, true)
|
|
89
79
|
}
|
|
90
80
|
|
|
91
81
|
// 9. Let permessageDeflate be a user-agent defined
|
|
@@ -95,7 +85,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
95
85
|
|
|
96
86
|
// 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
|
|
97
87
|
// request’s header list.
|
|
98
|
-
request.headersList.append('sec-websocket-extensions', permessageDeflate)
|
|
88
|
+
request.headersList.append('sec-websocket-extensions', permessageDeflate, true)
|
|
99
89
|
|
|
100
90
|
// 11. Fetch request with useParallelQueue set to true, and
|
|
101
91
|
// processResponse given response being these steps:
|
|
@@ -107,7 +97,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
107
97
|
// 1. If response is a network error or its status is not 101,
|
|
108
98
|
// fail the WebSocket connection.
|
|
109
99
|
if (response.type === 'error' || response.status !== 101) {
|
|
110
|
-
failWebsocketConnection(
|
|
100
|
+
failWebsocketConnection(handler, 'Received network error or non-101 status code.')
|
|
111
101
|
return
|
|
112
102
|
}
|
|
113
103
|
|
|
@@ -116,7 +106,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
116
106
|
// header list results in null, failure, or the empty byte
|
|
117
107
|
// sequence, then fail the WebSocket connection.
|
|
118
108
|
if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
|
|
119
|
-
failWebsocketConnection(
|
|
109
|
+
failWebsocketConnection(handler, 'Server did not respond with sent protocols.')
|
|
120
110
|
return
|
|
121
111
|
}
|
|
122
112
|
|
|
@@ -131,7 +121,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
131
121
|
// insensitive match for the value "websocket", the client MUST
|
|
132
122
|
// _Fail the WebSocket Connection_.
|
|
133
123
|
if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
|
|
134
|
-
failWebsocketConnection(
|
|
124
|
+
failWebsocketConnection(handler, 'Server did not set Upgrade header to "websocket".')
|
|
135
125
|
return
|
|
136
126
|
}
|
|
137
127
|
|
|
@@ -140,7 +130,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
140
130
|
// ASCII case-insensitive match for the value "Upgrade", the client
|
|
141
131
|
// MUST _Fail the WebSocket Connection_.
|
|
142
132
|
if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
|
|
143
|
-
failWebsocketConnection(
|
|
133
|
+
failWebsocketConnection(handler, 'Server did not set Connection header to "upgrade".')
|
|
144
134
|
return
|
|
145
135
|
}
|
|
146
136
|
|
|
@@ -154,7 +144,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
154
144
|
const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
|
|
155
145
|
const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
|
|
156
146
|
if (secWSAccept !== digest) {
|
|
157
|
-
failWebsocketConnection(
|
|
147
|
+
failWebsocketConnection(handler, 'Incorrect hash received in Sec-WebSocket-Accept header.')
|
|
158
148
|
return
|
|
159
149
|
}
|
|
160
150
|
|
|
@@ -172,7 +162,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
172
162
|
extensions = parseExtensions(secExtension)
|
|
173
163
|
|
|
174
164
|
if (!extensions.has('permessage-deflate')) {
|
|
175
|
-
failWebsocketConnection(
|
|
165
|
+
failWebsocketConnection(handler, 'Sec-WebSocket-Extensions header does not match.')
|
|
176
166
|
return
|
|
177
167
|
}
|
|
178
168
|
}
|
|
@@ -193,14 +183,14 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
193
183
|
// the selected subprotocol values in its response for the connection to
|
|
194
184
|
// be established.
|
|
195
185
|
if (!requestProtocols.includes(secProtocol)) {
|
|
196
|
-
failWebsocketConnection(
|
|
186
|
+
failWebsocketConnection(handler, 'Protocol was not set in the opening handshake.')
|
|
197
187
|
return
|
|
198
188
|
}
|
|
199
189
|
}
|
|
200
190
|
|
|
201
|
-
response.socket.on('data', onSocketData)
|
|
202
|
-
response.socket.on('close', onSocketClose)
|
|
203
|
-
response.socket.on('error', onSocketError)
|
|
191
|
+
response.socket.on('data', handler.onSocketData)
|
|
192
|
+
response.socket.on('close', handler.onSocketClose)
|
|
193
|
+
response.socket.on('error', handler.onSocketError)
|
|
204
194
|
|
|
205
195
|
if (channels.open.hasSubscribers) {
|
|
206
196
|
channels.open.publish({
|
|
@@ -210,159 +200,21 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
210
200
|
})
|
|
211
201
|
}
|
|
212
202
|
|
|
213
|
-
|
|
203
|
+
handler.onConnectionEstablished(response, extensions)
|
|
214
204
|
}
|
|
215
205
|
})
|
|
216
206
|
|
|
217
207
|
return controller
|
|
218
208
|
}
|
|
219
209
|
|
|
220
|
-
function closeWebSocketConnection (ws, code, reason, reasonByteLength) {
|
|
221
|
-
if (isClosing(ws) || isClosed(ws)) {
|
|
222
|
-
// If this's ready state is CLOSING (2) or CLOSED (3)
|
|
223
|
-
// Do nothing.
|
|
224
|
-
} else if (!isEstablished(ws)) {
|
|
225
|
-
// If the WebSocket connection is not yet established
|
|
226
|
-
// Fail the WebSocket connection and set this's ready state
|
|
227
|
-
// to CLOSING (2).
|
|
228
|
-
failWebsocketConnection(ws, 'Connection was closed before it was established.')
|
|
229
|
-
ws[kReadyState] = states.CLOSING
|
|
230
|
-
} else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) {
|
|
231
|
-
// If the WebSocket closing handshake has not yet been started
|
|
232
|
-
// Start the WebSocket closing handshake and set this's ready
|
|
233
|
-
// state to CLOSING (2).
|
|
234
|
-
// - If neither code nor reason is present, the WebSocket Close
|
|
235
|
-
// message must not have a body.
|
|
236
|
-
// - If code is present, then the status code to use in the
|
|
237
|
-
// WebSocket Close message must be the integer given by code.
|
|
238
|
-
// - If reason is also present, then reasonBytes must be
|
|
239
|
-
// provided in the Close message after the status code.
|
|
240
|
-
|
|
241
|
-
ws[kSentClose] = sentCloseFrameState.PROCESSING
|
|
242
|
-
|
|
243
|
-
const frame = new WebsocketFrameSend()
|
|
244
|
-
|
|
245
|
-
// If neither code nor reason is present, the WebSocket Close
|
|
246
|
-
// message must not have a body.
|
|
247
|
-
|
|
248
|
-
// If code is present, then the status code to use in the
|
|
249
|
-
// WebSocket Close message must be the integer given by code.
|
|
250
|
-
if (code !== undefined && reason === undefined) {
|
|
251
|
-
frame.frameData = Buffer.allocUnsafe(2)
|
|
252
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
253
|
-
} else if (code !== undefined && reason !== undefined) {
|
|
254
|
-
// If reason is also present, then reasonBytes must be
|
|
255
|
-
// provided in the Close message after the status code.
|
|
256
|
-
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
|
|
257
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
258
|
-
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
259
|
-
frame.frameData.write(reason, 2, 'utf-8')
|
|
260
|
-
} else {
|
|
261
|
-
frame.frameData = emptyBuffer
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/** @type {import('stream').Duplex} */
|
|
265
|
-
const socket = ws[kResponse].socket
|
|
266
|
-
|
|
267
|
-
socket.write(frame.createFrame(opcodes.CLOSE))
|
|
268
|
-
|
|
269
|
-
ws[kSentClose] = sentCloseFrameState.SENT
|
|
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
|
-
|
|
282
|
-
/**
|
|
283
|
-
* @param {Buffer} chunk
|
|
284
|
-
*/
|
|
285
|
-
function onSocketData (chunk) {
|
|
286
|
-
if (!this.ws[kByteParser].write(chunk)) {
|
|
287
|
-
this.pause()
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
210
|
/**
|
|
292
|
-
* @
|
|
293
|
-
* @
|
|
211
|
+
* @param {import('./websocket').Handler} handler
|
|
212
|
+
* @param {number} code
|
|
213
|
+
* @param {any} reason
|
|
214
|
+
* @param {number} reasonByteLength
|
|
294
215
|
*/
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
const { [kResponse]: response } = ws
|
|
298
|
-
|
|
299
|
-
response.socket.off('data', onSocketData)
|
|
300
|
-
response.socket.off('close', onSocketClose)
|
|
301
|
-
response.socket.off('error', onSocketError)
|
|
302
|
-
|
|
303
|
-
// If the TCP connection was closed after the
|
|
304
|
-
// WebSocket closing handshake was completed, the WebSocket connection
|
|
305
|
-
// is said to have been closed _cleanly_.
|
|
306
|
-
const wasClean = ws[kSentClose] === sentCloseFrameState.SENT && ws[kReceivedClose]
|
|
307
|
-
|
|
308
|
-
let code = 1005
|
|
309
|
-
let reason = ''
|
|
310
|
-
|
|
311
|
-
const result = ws[kByteParser].closingInfo
|
|
312
|
-
|
|
313
|
-
if (result && !result.error) {
|
|
314
|
-
code = result.code ?? 1005
|
|
315
|
-
reason = result.reason
|
|
316
|
-
} else if (!ws[kReceivedClose]) {
|
|
317
|
-
// If _The WebSocket
|
|
318
|
-
// Connection is Closed_ and no Close control frame was received by the
|
|
319
|
-
// endpoint (such as could occur if the underlying transport connection
|
|
320
|
-
// is lost), _The WebSocket Connection Close Code_ is considered to be
|
|
321
|
-
// 1006.
|
|
322
|
-
code = 1006
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// 1. Change the ready state to CLOSED (3).
|
|
326
|
-
ws[kReadyState] = states.CLOSED
|
|
327
|
-
|
|
328
|
-
// 2. If the user agent was required to fail the WebSocket
|
|
329
|
-
// connection, or if the WebSocket connection was closed
|
|
330
|
-
// after being flagged as full, fire an event named error
|
|
331
|
-
// at the WebSocket object.
|
|
332
|
-
// TODO
|
|
333
|
-
|
|
334
|
-
// 3. Fire an event named close at the WebSocket object,
|
|
335
|
-
// using CloseEvent, with the wasClean attribute
|
|
336
|
-
// initialized to true if the connection closed cleanly
|
|
337
|
-
// and false otherwise, the code attribute initialized to
|
|
338
|
-
// the WebSocket connection close code, and the reason
|
|
339
|
-
// attribute initialized to the result of applying UTF-8
|
|
340
|
-
// decode without BOM to the WebSocket connection close
|
|
341
|
-
// reason.
|
|
342
|
-
// TODO: process.nextTick
|
|
343
|
-
fireEvent('close', ws, (type, init) => new CloseEvent(type, init), {
|
|
344
|
-
wasClean, code, reason
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
if (channels.close.hasSubscribers) {
|
|
348
|
-
channels.close.publish({
|
|
349
|
-
websocket: ws,
|
|
350
|
-
code,
|
|
351
|
-
reason
|
|
352
|
-
})
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function onSocketError (error) {
|
|
357
|
-
const { ws } = this
|
|
358
|
-
|
|
359
|
-
ws[kReadyState] = states.CLOSING
|
|
360
|
-
|
|
361
|
-
if (channels.socketError.hasSubscribers) {
|
|
362
|
-
channels.socketError.publish(error)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
this.destroy()
|
|
216
|
+
function closeWebSocketConnection (handler, code, reason, reasonByteLength) {
|
|
217
|
+
handler.onClose(code, reason, reasonByteLength)
|
|
366
218
|
}
|
|
367
219
|
|
|
368
220
|
module.exports = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { maxUnsigned16Bit } = require('./constants')
|
|
3
|
+
const { maxUnsigned16Bit, opcodes } = require('./constants')
|
|
4
4
|
|
|
5
|
-
const BUFFER_SIZE =
|
|
5
|
+
const BUFFER_SIZE = 8 * 1024
|
|
6
6
|
|
|
7
7
|
/** @type {import('crypto')} */
|
|
8
8
|
let crypto
|
|
@@ -27,7 +27,7 @@ try {
|
|
|
27
27
|
function generateMask () {
|
|
28
28
|
if (bufIdx === BUFFER_SIZE) {
|
|
29
29
|
bufIdx = 0
|
|
30
|
-
crypto.randomFillSync((buffer ??= Buffer.
|
|
30
|
+
crypto.randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
|
|
31
31
|
}
|
|
32
32
|
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
|
|
33
33
|
}
|
|
@@ -89,6 +89,48 @@ class WebsocketFrameSend {
|
|
|
89
89
|
|
|
90
90
|
return buffer
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Uint8Array} buffer
|
|
95
|
+
*/
|
|
96
|
+
static createFastTextFrame (buffer) {
|
|
97
|
+
const maskKey = generateMask()
|
|
98
|
+
|
|
99
|
+
const bodyLength = buffer.length
|
|
100
|
+
|
|
101
|
+
// mask body
|
|
102
|
+
for (let i = 0; i < bodyLength; ++i) {
|
|
103
|
+
buffer[i] ^= maskKey[i & 3]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let payloadLength = bodyLength
|
|
107
|
+
let offset = 6
|
|
108
|
+
|
|
109
|
+
if (bodyLength > maxUnsigned16Bit) {
|
|
110
|
+
offset += 8 // payload length is next 8 bytes
|
|
111
|
+
payloadLength = 127
|
|
112
|
+
} else if (bodyLength > 125) {
|
|
113
|
+
offset += 2 // payload length is next 2 bytes
|
|
114
|
+
payloadLength = 126
|
|
115
|
+
}
|
|
116
|
+
const head = Buffer.allocUnsafeSlow(offset)
|
|
117
|
+
|
|
118
|
+
head[0] = 0x80 /* FIN */ | opcodes.TEXT /* opcode TEXT */
|
|
119
|
+
head[1] = payloadLength | 0x80 /* MASK */
|
|
120
|
+
head[offset - 4] = maskKey[0]
|
|
121
|
+
head[offset - 3] = maskKey[1]
|
|
122
|
+
head[offset - 2] = maskKey[2]
|
|
123
|
+
head[offset - 1] = maskKey[3]
|
|
124
|
+
|
|
125
|
+
if (payloadLength === 126) {
|
|
126
|
+
head.writeUInt16BE(bodyLength, 2)
|
|
127
|
+
} else if (payloadLength === 127) {
|
|
128
|
+
head[2] = head[3] = 0
|
|
129
|
+
head.writeUIntBE(bodyLength, 4, 6)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [head, buffer]
|
|
133
|
+
}
|
|
92
134
|
}
|
|
93
135
|
|
|
94
136
|
module.exports = {
|