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,30 +3,45 @@
3
3
  const { webidl } = require('../fetch/webidl')
4
4
  const { URLSerializer } = require('../fetch/data-url')
5
5
  const { environmentSettingsObject } = require('../fetch/util')
6
- const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints } = require('./constants')
7
- const {
8
- kWebSocketURL,
9
- kReadyState,
10
- kController,
11
- kBinaryType,
12
- kResponse,
13
- kSentClose,
14
- kByteParser
15
- } = require('./symbols')
6
+ const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes, emptyBuffer } = require('./constants')
16
7
  const {
17
8
  isConnecting,
18
9
  isEstablished,
19
10
  isClosing,
20
11
  isValidSubprotocol,
21
- fireEvent
12
+ fireEvent,
13
+ failWebsocketConnection,
14
+ utf8Decode,
15
+ toArrayBuffer,
16
+ isClosed
22
17
  } = require('./util')
23
18
  const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
24
19
  const { ByteParser } = require('./receiver')
25
- const { kEnumerableProperty, isBlobLike } = require('../../core/util')
20
+ const { kEnumerableProperty } = require('../../core/util')
26
21
  const { getGlobalDispatcher } = require('../../global')
27
22
  const { types } = require('node:util')
28
- const { ErrorEvent, CloseEvent } = require('./events')
23
+ const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
29
24
  const { SendQueue } = require('./sender')
25
+ const { WebsocketFrameSend } = require('./frame')
26
+ const { channels } = require('../../core/diagnostics')
27
+
28
+ /**
29
+ * @typedef {object} Handler
30
+ * @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
31
+ * @property {(reason: any) => void} onFail
32
+ * @property {(opcode: number, data: Buffer) => void} onMessage
33
+ * @property {(code: number, reason: any, reasonByteLength: number) => void} onClose
34
+ * @property {(error: Error) => void} onParserError
35
+ * @property {() => void} onParserDrain
36
+ * @property {(chunk: Buffer) => void} onSocketData
37
+ * @property {(err: Error) => void} onSocketError
38
+ * @property {() => void} onSocketClose
39
+ *
40
+ * @property {number} readyState
41
+ * @property {import('stream').Duplex} socket
42
+ * @property {number} closeState
43
+ * @property {boolean} receivedClose
44
+ */
30
45
 
31
46
  // https://websockets.spec.whatwg.org/#interface-definition
32
47
  class WebSocket extends EventTarget {
@@ -44,6 +59,42 @@ class WebSocket extends EventTarget {
44
59
  /** @type {SendQueue} */
45
60
  #sendQueue
46
61
 
62
+ /** @type {Handler} */
63
+ #handler = {
64
+ onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
65
+ onFail: (reason) => this.#onFail(reason),
66
+ onMessage: (opcode, data) => this.#onMessage(opcode, data),
67
+ onClose: (code, reason, reasonByteLength) => this.#onClose(code, reason, reasonByteLength),
68
+ onParserError: (err) => this.#onParserError(err),
69
+ onParserDrain: () => this.#onParserDrain(),
70
+ onSocketData: (chunk) => {
71
+ if (!this.#parser.write(chunk)) {
72
+ this.#handler.socket.pause()
73
+ }
74
+ },
75
+ onSocketError: (err) => {
76
+ this.#handler.readyState = states.CLOSING
77
+
78
+ if (channels.socketError.hasSubscribers) {
79
+ channels.socketError.publish(err)
80
+ }
81
+
82
+ this.#handler.socket.destroy()
83
+ },
84
+ onSocketClose: () => this.#onSocketClose(),
85
+
86
+ readyState: states.CONNECTING,
87
+ socket: null,
88
+ closeState: sentCloseFrameState.NOT_SENT,
89
+ receivedClose: false
90
+ }
91
+
92
+ #url
93
+ #controller
94
+ #binaryType
95
+ /** @type {import('./receiver').ByteParser} */
96
+ #parser
97
+
47
98
  /**
48
99
  * @param {string} url
49
100
  * @param {string|string[]} protocols
@@ -56,7 +107,7 @@ class WebSocket extends EventTarget {
56
107
 
57
108
  const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
58
109
 
59
- url = webidl.converters.USVString(url, prefix, 'url')
110
+ url = webidl.converters.USVString(url)
60
111
  protocols = options.protocols
61
112
 
62
113
  // 1. Let baseURL be this's relevant settings object's API base URL.
@@ -113,7 +164,7 @@ class WebSocket extends EventTarget {
113
164
  }
114
165
 
115
166
  // 10. Set this's url to urlRecord.
116
- this[kWebSocketURL] = new URL(urlRecord.href)
167
+ this.#url = new URL(urlRecord.href)
117
168
 
118
169
  // 11. Let client be this's relevant settings object.
119
170
  const client = environmentSettingsObject.settingsObject
@@ -122,21 +173,20 @@ class WebSocket extends EventTarget {
122
173
 
123
174
  // 1. Establish a WebSocket connection given urlRecord, protocols,
124
175
  // and client.
125
- this[kController] = establishWebSocketConnection(
176
+ this.#controller = establishWebSocketConnection(
126
177
  urlRecord,
127
178
  protocols,
128
179
  client,
129
- this,
130
- (response, extensions) => this.#onConnectionEstablished(response, extensions),
180
+ this.#handler,
131
181
  options
132
182
  )
133
183
 
134
184
  // Each WebSocket object has an associated ready state, which is a
135
185
  // number representing the state of the connection. Initially it must
136
186
  // be CONNECTING (0).
137
- this[kReadyState] = WebSocket.CONNECTING
187
+ this.#handler.readyState = WebSocket.CONNECTING
138
188
 
139
- this[kSentClose] = sentCloseFrameState.NOT_SENT
189
+ this.#handler.closeState = sentCloseFrameState.NOT_SENT
140
190
 
141
191
  // The extensions attribute must initially return the empty string.
142
192
 
@@ -144,7 +194,7 @@ class WebSocket extends EventTarget {
144
194
 
145
195
  // Each WebSocket object has an associated binary type, which is a
146
196
  // BinaryType. Initially it must be "blob".
147
- this[kBinaryType] = 'blob'
197
+ this.#binaryType = 'blob'
148
198
  }
149
199
 
150
200
  /**
@@ -162,7 +212,7 @@ class WebSocket extends EventTarget {
162
212
  }
163
213
 
164
214
  if (reason !== undefined) {
165
- reason = webidl.converters.USVString(reason, prefix, 'reason')
215
+ reason = webidl.converters.USVString(reason)
166
216
  }
167
217
 
168
218
  // 1. If code is present, but is neither an integer equal to 1000 nor an
@@ -192,7 +242,7 @@ class WebSocket extends EventTarget {
192
242
  }
193
243
 
194
244
  // 3. Run the first matching steps from the following list:
195
- closeWebSocketConnection(this, code, reason, reasonByteLength)
245
+ closeWebSocketConnection(this.#handler, code, reason, reasonByteLength)
196
246
  }
197
247
 
198
248
  /**
@@ -209,7 +259,7 @@ class WebSocket extends EventTarget {
209
259
 
210
260
  // 1. If this's ready state is CONNECTING, then throw an
211
261
  // "InvalidStateError" DOMException.
212
- if (isConnecting(this)) {
262
+ if (isConnecting(this.#handler.readyState)) {
213
263
  throw new DOMException('Sent before connected.', 'InvalidStateError')
214
264
  }
215
265
 
@@ -217,7 +267,7 @@ class WebSocket extends EventTarget {
217
267
  // https://datatracker.ietf.org/doc/html/rfc6455#section-6.1
218
268
  // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
219
269
 
220
- if (!isEstablished(this) || isClosing(this)) {
270
+ if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {
221
271
  return
222
272
  }
223
273
 
@@ -234,12 +284,12 @@ class WebSocket extends EventTarget {
234
284
  // the bufferedAmount attribute by the number of bytes needed to
235
285
  // express the argument as UTF-8.
236
286
 
237
- const length = Buffer.byteLength(data)
287
+ const buffer = Buffer.from(data)
238
288
 
239
- this.#bufferedAmount += length
240
- this.#sendQueue.add(data, () => {
241
- this.#bufferedAmount -= length
242
- }, sendHints.string)
289
+ this.#bufferedAmount += buffer.byteLength
290
+ this.#sendQueue.add(buffer, () => {
291
+ this.#bufferedAmount -= buffer.byteLength
292
+ }, sendHints.text)
243
293
  } else if (types.isArrayBuffer(data)) {
244
294
  // If the WebSocket connection is established, and the WebSocket
245
295
  // closing handshake has not yet started, then the user agent must
@@ -274,7 +324,7 @@ class WebSocket extends EventTarget {
274
324
  this.#sendQueue.add(data, () => {
275
325
  this.#bufferedAmount -= data.byteLength
276
326
  }, sendHints.typedArray)
277
- } else if (isBlobLike(data)) {
327
+ } else if (data instanceof Blob) {
278
328
  // If the WebSocket connection is established, and the WebSocket
279
329
  // closing handshake has not yet started, then the user agent must
280
330
  // send a WebSocket Message comprised of data using a binary frame
@@ -297,7 +347,7 @@ class WebSocket extends EventTarget {
297
347
  webidl.brandCheck(this, WebSocket)
298
348
 
299
349
  // The readyState getter steps are to return this's ready state.
300
- return this[kReadyState]
350
+ return this.#handler.readyState
301
351
  }
302
352
 
303
353
  get bufferedAmount () {
@@ -310,7 +360,7 @@ class WebSocket extends EventTarget {
310
360
  webidl.brandCheck(this, WebSocket)
311
361
 
312
362
  // The url getter steps are to return this's url, serialized.
313
- return URLSerializer(this[kWebSocketURL])
363
+ return URLSerializer(this.#url)
314
364
  }
315
365
 
316
366
  get extensions () {
@@ -412,16 +462,16 @@ class WebSocket extends EventTarget {
412
462
  get binaryType () {
413
463
  webidl.brandCheck(this, WebSocket)
414
464
 
415
- return this[kBinaryType]
465
+ return this.#binaryType
416
466
  }
417
467
 
418
468
  set binaryType (type) {
419
469
  webidl.brandCheck(this, WebSocket)
420
470
 
421
471
  if (type !== 'blob' && type !== 'arraybuffer') {
422
- this[kBinaryType] = 'blob'
472
+ this.#binaryType = 'blob'
423
473
  } else {
424
- this[kBinaryType] = type
474
+ this.#binaryType = type
425
475
  }
426
476
  }
427
477
 
@@ -431,19 +481,17 @@ class WebSocket extends EventTarget {
431
481
  #onConnectionEstablished (response, parsedExtensions) {
432
482
  // processResponse is called when the "response’s header list has been received and initialized."
433
483
  // once this happens, the connection is open
434
- this[kResponse] = response
484
+ this.#handler.socket = response.socket
435
485
 
436
- const parser = new ByteParser(this, parsedExtensions)
437
- parser.on('drain', onParserDrain)
438
- parser.on('error', onParserError.bind(this))
439
-
440
- response.socket.ws = this
441
- this[kByteParser] = parser
486
+ const parser = new ByteParser(this.#handler, parsedExtensions)
487
+ parser.on('drain', () => this.#handler.onParserDrain())
488
+ parser.on('error', (err) => this.#handler.onParserError(err))
442
489
 
490
+ this.#parser = parser
443
491
  this.#sendQueue = new SendQueue(response.socket)
444
492
 
445
493
  // 1. Change the ready state to OPEN (1).
446
- this[kReadyState] = states.OPEN
494
+ this.#handler.readyState = states.OPEN
447
495
 
448
496
  // 2. Change the extensions attribute’s value to the extensions in use, if
449
497
  // it is not the null value.
@@ -466,6 +514,202 @@ class WebSocket extends EventTarget {
466
514
  // 4. Fire an event named open at the WebSocket object.
467
515
  fireEvent('open', this)
468
516
  }
517
+
518
+ #onFail (reason) {
519
+ this.#controller.abort()
520
+
521
+ if (this.#handler.socket && !this.#handler.socket.destroyed) {
522
+ this.#handler.socket.destroy()
523
+ }
524
+
525
+ this.#handler.readyState = states.CLOSED
526
+
527
+ if (reason) {
528
+ // TODO: process.nextTick
529
+ fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
530
+ error: new Error(reason),
531
+ message: reason
532
+ })
533
+ }
534
+ }
535
+
536
+ #onMessage (type, data) {
537
+ // 1. If ready state is not OPEN (1), then return.
538
+ if (this.#handler.readyState !== states.OPEN) {
539
+ return
540
+ }
541
+
542
+ // 2. Let dataForEvent be determined by switching on type and binary type:
543
+ let dataForEvent
544
+
545
+ if (type === opcodes.TEXT) {
546
+ // -> type indicates that the data is Text
547
+ // a new DOMString containing data
548
+ try {
549
+ dataForEvent = utf8Decode(data)
550
+ } catch {
551
+ failWebsocketConnection(this.#handler, 'Received invalid UTF-8 in text frame.')
552
+ return
553
+ }
554
+ } else if (type === opcodes.BINARY) {
555
+ if (this.#binaryType === 'blob') {
556
+ // -> type indicates that the data is Binary and binary type is "blob"
557
+ // a new Blob object, created in the relevant Realm of the WebSocket
558
+ // object, that represents data as its raw data
559
+ dataForEvent = new Blob([data])
560
+ } else {
561
+ // -> type indicates that the data is Binary and binary type is "arraybuffer"
562
+ // a new ArrayBuffer object, created in the relevant Realm of the
563
+ // WebSocket object, whose contents are data
564
+ dataForEvent = toArrayBuffer(data)
565
+ }
566
+ }
567
+
568
+ // 3. Fire an event named message at the WebSocket object, using MessageEvent,
569
+ // with the origin attribute initialized to the serialization of the WebSocket
570
+ // object’s url's origin, and the data attribute initialized to dataForEvent.
571
+ fireEvent('message', this, createFastMessageEvent, {
572
+ origin: this.#url.origin,
573
+ data: dataForEvent
574
+ })
575
+ }
576
+
577
+ #onClose (code, reason, reasonByteLength) {
578
+ if (isClosing(this.#handler.readyState) || isClosed(this.#handler.readyState)) {
579
+ // If this's ready state is CLOSING (2) or CLOSED (3)
580
+ // Do nothing.
581
+ } else if (!isEstablished(this.#handler.readyState)) {
582
+ // If the WebSocket connection is not yet established
583
+ // Fail the WebSocket connection and set this's ready state
584
+ // to CLOSING (2).
585
+ failWebsocketConnection(this.#handler, 'Connection was closed before it was established.')
586
+ this.#handler.readyState = states.CLOSING
587
+ } else if (this.#handler.closeState === sentCloseFrameState.NOT_SENT) {
588
+ // If the WebSocket closing handshake has not yet been started
589
+ // Start the WebSocket closing handshake and set this's ready
590
+ // state to CLOSING (2).
591
+ // - If neither code nor reason is present, the WebSocket Close
592
+ // message must not have a body.
593
+ // - If code is present, then the status code to use in the
594
+ // WebSocket Close message must be the integer given by code.
595
+ // - If reason is also present, then reasonBytes must be
596
+ // provided in the Close message after the status code.
597
+
598
+ this.#handler.closeState = sentCloseFrameState.PROCESSING
599
+
600
+ const frame = new WebsocketFrameSend()
601
+
602
+ // If neither code nor reason is present, the WebSocket Close
603
+ // message must not have a body.
604
+
605
+ // If code is present, then the status code to use in the
606
+ // WebSocket Close message must be the integer given by code.
607
+ if (code !== undefined && reason === undefined) {
608
+ frame.frameData = Buffer.allocUnsafe(2)
609
+ frame.frameData.writeUInt16BE(code, 0)
610
+ } else if (code !== undefined && reason !== undefined) {
611
+ // If reason is also present, then reasonBytes must be
612
+ // provided in the Close message after the status code.
613
+ frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
614
+ frame.frameData.writeUInt16BE(code, 0)
615
+ // the body MAY contain UTF-8-encoded data with value /reason/
616
+ frame.frameData.write(reason, 2, 'utf-8')
617
+ } else {
618
+ frame.frameData = emptyBuffer
619
+ }
620
+
621
+ this.#handler.socket.write(frame.createFrame(opcodes.CLOSE))
622
+
623
+ this.#handler.closeState = sentCloseFrameState.SENT
624
+
625
+ // Upon either sending or receiving a Close control frame, it is said
626
+ // that _The WebSocket Closing Handshake is Started_ and that the
627
+ // WebSocket connection is in the CLOSING state.
628
+ this.#handler.readyState = states.CLOSING
629
+ } else {
630
+ // Otherwise
631
+ // Set this's ready state to CLOSING (2).
632
+ this.#handler.readyState = states.CLOSING
633
+ }
634
+ }
635
+
636
+ #onParserError (err) {
637
+ let message
638
+ let code
639
+
640
+ if (err instanceof CloseEvent) {
641
+ message = err.reason
642
+ code = err.code
643
+ } else {
644
+ message = err.message
645
+ }
646
+
647
+ fireEvent('error', this, () => new ErrorEvent('error', { error: err, message }))
648
+
649
+ closeWebSocketConnection(this.#handler, code)
650
+ }
651
+
652
+ #onParserDrain () {
653
+ this.#handler.socket.resume()
654
+ }
655
+
656
+ /**
657
+ * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
658
+ * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
659
+ */
660
+ #onSocketClose () {
661
+ // If the TCP connection was closed after the
662
+ // WebSocket closing handshake was completed, the WebSocket connection
663
+ // is said to have been closed _cleanly_.
664
+ const wasClean = this.#handler.closeState === sentCloseFrameState.SENT && this.#handler.receivedClose
665
+
666
+ let code = 1005
667
+ let reason = ''
668
+
669
+ const result = this.#parser.closingInfo
670
+
671
+ if (result && !result.error) {
672
+ code = result.code ?? 1005
673
+ reason = result.reason
674
+ } else if (!this.#handler.receivedClose) {
675
+ // If _The WebSocket
676
+ // Connection is Closed_ and no Close control frame was received by the
677
+ // endpoint (such as could occur if the underlying transport connection
678
+ // is lost), _The WebSocket Connection Close Code_ is considered to be
679
+ // 1006.
680
+ code = 1006
681
+ }
682
+
683
+ // 1. Change the ready state to CLOSED (3).
684
+ this.#handler.readyState = states.CLOSED
685
+
686
+ // 2. If the user agent was required to fail the WebSocket
687
+ // connection, or if the WebSocket connection was closed
688
+ // after being flagged as full, fire an event named error
689
+ // at the WebSocket object.
690
+ // TODO
691
+
692
+ // 3. Fire an event named close at the WebSocket object,
693
+ // using CloseEvent, with the wasClean attribute
694
+ // initialized to true if the connection closed cleanly
695
+ // and false otherwise, the code attribute initialized to
696
+ // the WebSocket connection close code, and the reason
697
+ // attribute initialized to the result of applying UTF-8
698
+ // decode without BOM to the WebSocket connection close
699
+ // reason.
700
+ // TODO: process.nextTick
701
+ fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
702
+ wasClean, code, reason
703
+ })
704
+
705
+ if (channels.close.hasSubscribers) {
706
+ channels.close.publish({
707
+ websocket: this,
708
+ code,
709
+ reason
710
+ })
711
+ }
712
+ }
469
713
  }
470
714
 
471
715
  // https://websockets.spec.whatwg.org/#dom-websocket-connecting
@@ -514,7 +758,7 @@ webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
514
758
  )
515
759
 
516
760
  webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
517
- if (webidl.util.Type(V) === 'Object' && Symbol.iterator in V) {
761
+ if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {
518
762
  return webidl.converters['sequence<DOMString>'](V)
519
763
  }
520
764
 
@@ -540,7 +784,7 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
540
784
  ])
541
785
 
542
786
  webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
543
- if (webidl.util.Type(V) === 'Object' && !(Symbol.iterator in V)) {
787
+ if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {
544
788
  return webidl.converters.WebSocketInit(V)
545
789
  }
546
790
 
@@ -548,39 +792,19 @@ webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = functio
548
792
  }
549
793
 
550
794
  webidl.converters.WebSocketSendData = function (V) {
551
- if (webidl.util.Type(V) === 'Object') {
552
- if (isBlobLike(V)) {
553
- return webidl.converters.Blob(V, { strict: false })
795
+ if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
796
+ if (V instanceof Blob) {
797
+ return V
554
798
  }
555
799
 
556
800
  if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
557
- return webidl.converters.BufferSource(V)
801
+ return V
558
802
  }
559
803
  }
560
804
 
561
805
  return webidl.converters.USVString(V)
562
806
  }
563
807
 
564
- function onParserDrain () {
565
- this.ws[kResponse].socket.resume()
566
- }
567
-
568
- function onParserError (err) {
569
- let message
570
- let code
571
-
572
- if (err instanceof CloseEvent) {
573
- message = err.reason
574
- code = err.code
575
- } else {
576
- message = err.message
577
- }
578
-
579
- fireEvent('error', this, () => new ErrorEvent('error', { error: err, message }))
580
-
581
- closeWebSocketConnection(this, code)
582
- }
583
-
584
808
  module.exports = {
585
809
  WebSocket
586
810
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.19.7",
3
+ "version": "7.0.0-alpha.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -65,19 +65,19 @@
65
65
  "build:node": "npx esbuild@0.19.10 index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js",
66
66
  "prebuild:wasm": "node build/wasm.js --prebuild",
67
67
  "build:wasm": "node build/wasm.js --docker",
68
- "lint": "standard | snazzy",
69
- "lint:fix": "standard --fix | snazzy",
68
+ "generate-pem": "node scripts/generate-pem.js",
69
+ "lint": "eslint --cache",
70
+ "lint:fix": "eslint --fix --cache",
70
71
  "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
71
- "test:javascript": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:jest",
72
- "test:javascript:withoutintl": "node scripts/generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:fetch:nobuild && npm run test:cache && npm run test:interceptors && npm run test:cookies && npm run test:eventsource:nobuild && npm run test:wpt:withoutintl && npm run test:node-test",
72
+ "test:javascript": "npm run test:javascript:no-jest && npm run test:jest",
73
+ "test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test",
74
+ "test:javascript:without-intl": "npm run test:javascript:no-jest",
73
75
  "test:busboy": "borp -p \"test/busboy/*.js\"",
74
76
  "test:cache": "borp -p \"test/cache/*.js\"",
75
77
  "test:cookies": "borp -p \"test/cookie/*.js\"",
76
- "test:eventsource": "npm run build:node && npm run test:eventsource:nobuild",
77
- "test:eventsource:nobuild": "borp --expose-gc -p \"test/eventsource/*.js\"",
78
+ "test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"",
78
79
  "test:fuzzing": "node test/fuzzing/fuzzing.test.js",
79
- "test:fetch": "npm run build:node && npm run test:fetch:nobuild",
80
- "test:fetch:nobuild": "borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
80
+ "test:fetch": "npm run build:node && borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy",
81
81
  "test:interceptors": "borp -p \"test/interceptors/*.js\"",
82
82
  "test:jest": "cross-env NODE_V8_COVERAGE= jest",
83
83
  "test:unit": "borp --expose-gc -p \"test/*.js\"",
@@ -85,12 +85,12 @@
85
85
  "test:node-test": "borp -p \"test/node-test/**/*.js\"",
86
86
  "test:tdd": "borp --expose-gc -p \"test/*.js\"",
87
87
  "test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w",
88
- "test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types && tsc ./types/*.d.ts --noEmit --typeRoots ./types",
88
+ "test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types",
89
89
  "test:webidl": "borp -p \"test/webidl/*.js\"",
90
90
  "test:websocket": "borp -p \"test/websocket/*.js\"",
91
91
  "test:websocket:autobahn": "node test/autobahn/client.js",
92
92
  "test:websocket:autobahn:report": "node test/autobahn/report.js",
93
- "test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.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",
93
+ "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",
94
94
  "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
95
95
  "coverage": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report",
96
96
  "coverage:ci": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci",
@@ -102,27 +102,24 @@
102
102
  "prepare": "husky && node ./scripts/platform-shell.js"
103
103
  },
104
104
  "devDependencies": {
105
- "@fastify/busboy": "2.1.1",
105
+ "@fastify/busboy": "3.0.0",
106
106
  "@matteo.collina/tspl": "^0.1.1",
107
107
  "@sinonjs/fake-timers": "^11.1.0",
108
- "@types/node": "^18.0.3",
108
+ "@types/node": "~18.17.19",
109
109
  "abort-controller": "^3.0.0",
110
- "borp": "^0.15.0",
110
+ "borp": "^0.17.0",
111
111
  "c8": "^10.0.0",
112
112
  "cross-env": "^7.0.3",
113
113
  "dns-packet": "^5.4.0",
114
+ "eslint": "^9.9.0",
114
115
  "fast-check": "^3.17.1",
115
- "form-data": "^4.0.0",
116
- "formdata-node": "^6.0.3",
117
116
  "https-pem": "^3.0.0",
118
117
  "husky": "^9.0.7",
119
118
  "jest": "^29.0.2",
120
- "jsdom": "^24.0.0",
119
+ "neostandard": "^0.11.2",
121
120
  "node-forge": "^1.3.1",
122
121
  "pre-commit": "^1.2.2",
123
122
  "proxy": "^2.1.1",
124
- "snazzy": "^9.0.0",
125
- "standard": "^17.0.0",
126
123
  "tsd": "^0.31.0",
127
124
  "typescript": "^5.0.2",
128
125
  "ws": "^8.11.0"
@@ -130,16 +127,6 @@
130
127
  "engines": {
131
128
  "node": ">=18.17"
132
129
  },
133
- "standard": {
134
- "env": [
135
- "jest"
136
- ],
137
- "ignore": [
138
- "lib/llhttp/constants.js",
139
- "lib/llhttp/utils.js",
140
- "test/fixtures/wpt"
141
- ]
142
- },
143
130
  "tsd": {
144
131
  "directory": "test/types",
145
132
  "compilerOptions": {
@@ -3,6 +3,8 @@
3
3
  const { readFileSync, writeFileSync } = require('node:fs')
4
4
  const { transcode } = require('node:buffer')
5
5
 
6
- const buffer = transcode(readFileSync('./undici-fetch.js'), 'utf8', 'latin1')
6
+ const buffer = transcode
7
+ ? transcode(readFileSync('./undici-fetch.js'), 'utf8', 'latin1')
8
+ : readFileSync('./undici-fetch.js')
7
9
 
8
10
  writeFileSync('./undici-fetch.js', buffer.toString('latin1'))