undici 6.19.7 → 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.
Files changed (115) hide show
  1. package/README.md +5 -9
  2. package/docs/docs/api/Agent.md +0 -3
  3. package/docs/docs/api/Client.md +0 -2
  4. package/docs/docs/api/Dispatcher.md +204 -6
  5. package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
  6. package/docs/docs/api/Fetch.md +1 -0
  7. package/docs/docs/api/Pool.md +0 -1
  8. package/docs/docs/api/RetryHandler.md +1 -1
  9. package/index.js +0 -4
  10. package/lib/api/api-connect.js +3 -1
  11. package/lib/api/api-pipeline.js +3 -4
  12. package/lib/api/api-request.js +29 -46
  13. package/lib/api/api-stream.js +36 -49
  14. package/lib/api/api-upgrade.js +5 -3
  15. package/lib/api/readable.js +71 -27
  16. package/lib/core/connect.js +39 -24
  17. package/lib/core/errors.js +17 -4
  18. package/lib/core/request.js +7 -5
  19. package/lib/core/symbols.js +0 -1
  20. package/lib/core/tree.js +6 -0
  21. package/lib/core/util.js +1 -11
  22. package/lib/dispatcher/agent.js +3 -17
  23. package/lib/dispatcher/balanced-pool.js +27 -11
  24. package/lib/dispatcher/client-h1.js +44 -39
  25. package/lib/dispatcher/client.js +3 -27
  26. package/lib/dispatcher/dispatcher-base.js +2 -34
  27. package/lib/dispatcher/dispatcher.js +3 -24
  28. package/lib/dispatcher/pool.js +3 -6
  29. package/lib/dispatcher/proxy-agent.js +3 -6
  30. package/lib/handler/decorator-handler.js +24 -0
  31. package/lib/handler/redirect-handler.js +9 -0
  32. package/lib/handler/retry-handler.js +22 -3
  33. package/lib/interceptor/dump.js +2 -2
  34. package/lib/interceptor/redirect.js +11 -14
  35. package/lib/interceptor/response-error.js +89 -0
  36. package/lib/llhttp/constants.d.ts +97 -0
  37. package/lib/llhttp/constants.js +412 -192
  38. package/lib/llhttp/constants.js.map +1 -0
  39. package/lib/llhttp/llhttp-wasm.js +11 -1
  40. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  41. package/lib/llhttp/utils.d.ts +2 -0
  42. package/lib/llhttp/utils.js +9 -9
  43. package/lib/llhttp/utils.js.map +1 -0
  44. package/lib/mock/mock-client.js +2 -2
  45. package/lib/mock/mock-pool.js +2 -2
  46. package/lib/mock/mock-symbols.js +1 -0
  47. package/lib/util/timers.js +324 -44
  48. package/lib/web/cookies/index.js +15 -13
  49. package/lib/web/cookies/parse.js +2 -2
  50. package/lib/web/eventsource/eventsource-stream.js +9 -8
  51. package/lib/web/eventsource/eventsource.js +10 -6
  52. package/lib/web/fetch/body.js +31 -11
  53. package/lib/web/fetch/data-url.js +1 -1
  54. package/lib/web/fetch/formdata-parser.js +1 -2
  55. package/lib/web/fetch/formdata.js +28 -37
  56. package/lib/web/fetch/headers.js +1 -1
  57. package/lib/web/fetch/index.js +7 -8
  58. package/lib/web/fetch/request.js +11 -28
  59. package/lib/web/fetch/response.js +13 -41
  60. package/lib/web/fetch/symbols.js +0 -1
  61. package/lib/web/fetch/util.js +3 -12
  62. package/lib/web/fetch/webidl.js +73 -62
  63. package/lib/web/websocket/connection.js +26 -174
  64. package/lib/web/websocket/constants.js +1 -1
  65. package/lib/web/websocket/frame.js +45 -3
  66. package/lib/web/websocket/receiver.js +28 -26
  67. package/lib/web/websocket/sender.js +18 -13
  68. package/lib/web/websocket/util.js +20 -74
  69. package/lib/web/websocket/websocket.js +294 -70
  70. package/package.json +16 -29
  71. package/scripts/strip-comments.js +3 -1
  72. package/types/agent.d.ts +7 -7
  73. package/types/api.d.ts +24 -24
  74. package/types/balanced-pool.d.ts +11 -11
  75. package/types/client.d.ts +11 -12
  76. package/types/diagnostics-channel.d.ts +10 -10
  77. package/types/dispatcher.d.ts +96 -97
  78. package/types/env-http-proxy-agent.d.ts +2 -2
  79. package/types/errors.d.ts +53 -47
  80. package/types/eventsource.d.ts +0 -2
  81. package/types/fetch.d.ts +8 -8
  82. package/types/formdata.d.ts +7 -7
  83. package/types/global-dispatcher.d.ts +4 -4
  84. package/types/global-origin.d.ts +5 -5
  85. package/types/handlers.d.ts +4 -4
  86. package/types/header.d.ts +157 -1
  87. package/types/index.d.ts +42 -46
  88. package/types/interceptors.d.ts +10 -8
  89. package/types/mock-agent.d.ts +18 -18
  90. package/types/mock-client.d.ts +4 -4
  91. package/types/mock-errors.d.ts +3 -3
  92. package/types/mock-interceptor.d.ts +19 -19
  93. package/types/mock-pool.d.ts +4 -4
  94. package/types/patch.d.ts +0 -42
  95. package/types/pool-stats.d.ts +8 -8
  96. package/types/pool.d.ts +12 -12
  97. package/types/proxy-agent.d.ts +4 -4
  98. package/types/readable.d.ts +14 -9
  99. package/types/retry-agent.d.ts +1 -1
  100. package/types/retry-handler.d.ts +8 -8
  101. package/types/util.d.ts +3 -3
  102. package/types/utility.d.ts +7 -0
  103. package/types/webidl.d.ts +22 -4
  104. package/types/websocket.d.ts +1 -3
  105. package/docs/docs/api/DispatchInterceptor.md +0 -60
  106. package/lib/interceptor/redirect-interceptor.js +0 -21
  107. package/lib/web/fetch/file.js +0 -126
  108. package/lib/web/fileapi/encoding.js +0 -290
  109. package/lib/web/fileapi/filereader.js +0 -344
  110. package/lib/web/fileapi/progressevent.js +0 -78
  111. package/lib/web/fileapi/symbols.js +0 -10
  112. package/lib/web/fileapi/util.js +0 -391
  113. package/lib/web/websocket/symbols.js +0 -12
  114. package/types/file.d.ts +0 -39
  115. package/types/filereader.d.ts +0 -54
@@ -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, opts) {
37
- if (opts?.strict !== false) {
38
- if (!(V instanceof I)) {
39
- const err = new TypeError('Illegal invocation')
40
- err.code = 'ERR_INVALID_THIS' // node compat.
41
- throw err
42
- }
43
- } else {
44
- if (V?.[Symbol.toStringTag] !== I.prototype[Symbol.toStringTag]) {
45
- const err = new TypeError('Illegal invocation')
46
- err.code = 'ERR_INVALID_THIS' // node compat.
47
- throw err
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 'Undefined'
73
- case 'boolean': return 'Boolean'
74
- case 'string': return 'String'
75
- case 'symbol': return 'Symbol'
76
- case 'number': return 'Number'
77
- case 'bigint': return 'BigInt'
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 'Null'
90
+ return NULL
82
91
  }
83
92
 
84
- return 'Object'
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 'Symbol':
260
+ case SYMBOL:
228
261
  return `Symbol(${V.description})`
229
- case 'Object':
262
+ case OBJECT:
230
263
  return inspect(V)
231
- case 'String':
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) !== 'Object') {
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) !== 'Object') {
318
+ if (webidl.util.Type(O) !== OBJECT) {
286
319
  throw webidl.errors.exception({
287
320
  header: prefix,
288
- message: `${argument} ("${webidl.util.Type(O)}") is not an Object.`
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, opts) => {
344
- if (opts?.strict !== false && !(V instanceof i)) {
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 (type === 'Null' || type === 'Undefined') {
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 = Object.hasOwn(options, 'defaultValue')
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 !== null) {
387
- value ??= defaultValue()
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) !== 'Object' ||
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) !== 'Object' ||
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) !== 'Object' || !types.isDataView(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, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
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').WebSocket} ws
34
- * @param {(response: any, extensions: string[] | undefined) => void} onEstablish
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, ws, onEstablish, options) {
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(ws, 'Received network error or non-101 status code.')
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(ws, 'Server did not respond with sent protocols.')
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(ws, 'Server did not set Upgrade header to "websocket".')
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(ws, 'Server did not set Connection header to "upgrade".')
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(ws, 'Incorrect hash received in Sec-WebSocket-Accept header.')
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(ws, 'Sec-WebSocket-Extensions header does not match.')
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(ws, 'Protocol was not set in the opening handshake.')
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
- onEstablish(response, extensions)
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
- * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
293
- * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
211
+ * @param {import('./websocket').Handler} handler
212
+ * @param {number} code
213
+ * @param {any} reason
214
+ * @param {number} reasonByteLength
294
215
  */
295
- function onSocketClose () {
296
- const { ws } = this
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 = {
@@ -47,7 +47,7 @@ const parserStates = {
47
47
  const emptyBuffer = Buffer.allocUnsafe(0)
48
48
 
49
49
  const sendHints = {
50
- string: 1,
50
+ text: 1,
51
51
  typedArray: 2,
52
52
  arrayBuffer: 3,
53
53
  blob: 4
@@ -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 = 16386
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.allocUnsafe(BUFFER_SIZE)), 0, BUFFER_SIZE)
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 = {