undici 6.20.0 → 7.0.0-alpha.2

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 (137) hide show
  1. package/README.md +6 -10
  2. package/docs/docs/api/Agent.md +0 -3
  3. package/docs/docs/api/Client.md +1 -3
  4. package/docs/docs/api/Debug.md +1 -1
  5. package/docs/docs/api/Dispatcher.md +60 -8
  6. package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
  7. package/docs/docs/api/Fetch.md +1 -0
  8. package/docs/docs/api/MockAgent.md +2 -0
  9. package/docs/docs/api/MockPool.md +2 -1
  10. package/docs/docs/api/Pool.md +0 -1
  11. package/docs/docs/api/RetryAgent.md +1 -1
  12. package/docs/docs/api/RetryHandler.md +1 -1
  13. package/docs/docs/api/WebSocket.md +45 -3
  14. package/index.js +6 -6
  15. package/lib/api/abort-signal.js +2 -0
  16. package/lib/api/api-connect.js +3 -1
  17. package/lib/api/api-pipeline.js +7 -6
  18. package/lib/api/api-request.js +32 -47
  19. package/lib/api/api-stream.js +39 -50
  20. package/lib/api/api-upgrade.js +5 -3
  21. package/lib/api/readable.js +261 -64
  22. package/lib/api/util.js +2 -0
  23. package/lib/core/constants.js +11 -9
  24. package/lib/core/diagnostics.js +122 -128
  25. package/lib/core/errors.js +4 -4
  26. package/lib/core/request.js +11 -9
  27. package/lib/core/symbols.js +2 -1
  28. package/lib/core/tree.js +9 -1
  29. package/lib/core/util.js +219 -48
  30. package/lib/dispatcher/agent.js +3 -17
  31. package/lib/dispatcher/balanced-pool.js +5 -8
  32. package/lib/dispatcher/client-h1.js +278 -54
  33. package/lib/dispatcher/client-h2.js +1 -1
  34. package/lib/dispatcher/client.js +23 -34
  35. package/lib/dispatcher/dispatcher-base.js +2 -34
  36. package/lib/dispatcher/dispatcher.js +3 -24
  37. package/lib/dispatcher/fixed-queue.js +91 -49
  38. package/lib/dispatcher/pool-stats.js +2 -0
  39. package/lib/dispatcher/pool.js +3 -6
  40. package/lib/dispatcher/proxy-agent.js +6 -7
  41. package/lib/handler/decorator-handler.js +24 -0
  42. package/lib/handler/redirect-handler.js +11 -2
  43. package/lib/handler/retry-handler.js +12 -3
  44. package/lib/interceptor/dns.js +346 -0
  45. package/lib/interceptor/dump.js +2 -2
  46. package/lib/interceptor/redirect.js +11 -14
  47. package/lib/interceptor/response-error.js +4 -1
  48. package/lib/llhttp/constants.d.ts +97 -0
  49. package/lib/llhttp/constants.js +412 -192
  50. package/lib/llhttp/constants.js.map +1 -0
  51. package/lib/llhttp/llhttp-wasm.js +11 -1
  52. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  53. package/lib/llhttp/utils.d.ts +2 -0
  54. package/lib/llhttp/utils.js +9 -9
  55. package/lib/llhttp/utils.js.map +1 -0
  56. package/lib/mock/mock-agent.js +5 -8
  57. package/lib/mock/mock-client.js +9 -4
  58. package/lib/mock/mock-errors.js +3 -1
  59. package/lib/mock/mock-interceptor.js +8 -6
  60. package/lib/mock/mock-pool.js +9 -4
  61. package/lib/mock/mock-symbols.js +3 -1
  62. package/lib/mock/mock-utils.js +29 -5
  63. package/lib/web/cache/cache.js +24 -21
  64. package/lib/web/cache/cachestorage.js +1 -1
  65. package/lib/web/cookies/index.js +17 -13
  66. package/lib/web/cookies/parse.js +2 -2
  67. package/lib/web/eventsource/eventsource-stream.js +9 -8
  68. package/lib/web/eventsource/eventsource.js +10 -6
  69. package/lib/web/fetch/body.js +42 -36
  70. package/lib/web/fetch/constants.js +35 -26
  71. package/lib/web/fetch/data-url.js +1 -1
  72. package/lib/web/fetch/formdata-parser.js +2 -2
  73. package/lib/web/fetch/formdata.js +65 -54
  74. package/lib/web/fetch/headers.js +117 -85
  75. package/lib/web/fetch/index.js +55 -62
  76. package/lib/web/fetch/request.js +135 -77
  77. package/lib/web/fetch/response.js +86 -56
  78. package/lib/web/fetch/util.js +90 -64
  79. package/lib/web/fetch/webidl.js +99 -64
  80. package/lib/web/websocket/connection.js +76 -147
  81. package/lib/web/websocket/constants.js +3 -4
  82. package/lib/web/websocket/events.js +4 -2
  83. package/lib/web/websocket/frame.js +45 -3
  84. package/lib/web/websocket/receiver.js +29 -33
  85. package/lib/web/websocket/sender.js +18 -13
  86. package/lib/web/websocket/stream/websocketerror.js +83 -0
  87. package/lib/web/websocket/stream/websocketstream.js +485 -0
  88. package/lib/web/websocket/util.js +128 -77
  89. package/lib/web/websocket/websocket.js +234 -135
  90. package/package.json +20 -33
  91. package/scripts/strip-comments.js +3 -1
  92. package/types/agent.d.ts +7 -7
  93. package/types/api.d.ts +24 -24
  94. package/types/balanced-pool.d.ts +11 -11
  95. package/types/client.d.ts +11 -12
  96. package/types/diagnostics-channel.d.ts +10 -10
  97. package/types/dispatcher.d.ts +96 -97
  98. package/types/env-http-proxy-agent.d.ts +2 -2
  99. package/types/errors.d.ts +53 -47
  100. package/types/fetch.d.ts +8 -8
  101. package/types/formdata.d.ts +7 -7
  102. package/types/global-dispatcher.d.ts +4 -4
  103. package/types/global-origin.d.ts +5 -5
  104. package/types/handlers.d.ts +4 -4
  105. package/types/header.d.ts +157 -1
  106. package/types/index.d.ts +42 -46
  107. package/types/interceptors.d.ts +22 -8
  108. package/types/mock-agent.d.ts +21 -18
  109. package/types/mock-client.d.ts +4 -4
  110. package/types/mock-errors.d.ts +3 -3
  111. package/types/mock-interceptor.d.ts +19 -19
  112. package/types/mock-pool.d.ts +4 -4
  113. package/types/patch.d.ts +0 -4
  114. package/types/pool-stats.d.ts +8 -8
  115. package/types/pool.d.ts +12 -12
  116. package/types/proxy-agent.d.ts +4 -4
  117. package/types/readable.d.ts +22 -14
  118. package/types/retry-agent.d.ts +1 -1
  119. package/types/retry-handler.d.ts +8 -8
  120. package/types/util.d.ts +3 -3
  121. package/types/utility.d.ts +7 -0
  122. package/types/webidl.d.ts +44 -6
  123. package/types/websocket.d.ts +34 -1
  124. package/docs/docs/api/DispatchInterceptor.md +0 -60
  125. package/lib/interceptor/redirect-interceptor.js +0 -21
  126. package/lib/mock/pluralizer.js +0 -29
  127. package/lib/web/cache/symbols.js +0 -5
  128. package/lib/web/fetch/file.js +0 -126
  129. package/lib/web/fetch/symbols.js +0 -9
  130. package/lib/web/fileapi/encoding.js +0 -290
  131. package/lib/web/fileapi/filereader.js +0 -344
  132. package/lib/web/fileapi/progressevent.js +0 -78
  133. package/lib/web/fileapi/symbols.js +0 -10
  134. package/lib/web/fileapi/util.js +0 -391
  135. package/lib/web/websocket/symbols.js +0 -12
  136. package/types/file.d.ts +0 -39
  137. package/types/filereader.d.ts +0 -54
@@ -3,30 +3,44 @@
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 } = 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
+ getURLRecord
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 { channels } = require('../../core/diagnostics')
26
+
27
+ /**
28
+ * @typedef {object} Handler
29
+ * @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
30
+ * @property {(code: number, reason: any) => void} onFail
31
+ * @property {(opcode: number, data: Buffer) => void} onMessage
32
+ * @property {(error: Error) => void} onParserError
33
+ * @property {() => void} onParserDrain
34
+ * @property {(chunk: Buffer) => void} onSocketData
35
+ * @property {(err: Error) => void} onSocketError
36
+ * @property {() => void} onSocketClose
37
+ *
38
+ * @property {number} readyState
39
+ * @property {import('stream').Duplex} socket
40
+ * @property {Set<number>} closeState
41
+ * @property {import('../fetch/index').Fetch} controller
42
+ * @property {boolean} [wasEverConnected=false]
43
+ */
30
44
 
31
45
  // https://websockets.spec.whatwg.org/#interface-definition
32
46
  class WebSocket extends EventTarget {
@@ -44,6 +58,41 @@ class WebSocket extends EventTarget {
44
58
  /** @type {SendQueue} */
45
59
  #sendQueue
46
60
 
61
+ /** @type {Handler} */
62
+ #handler = {
63
+ onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
64
+ onFail: (code, reason) => this.#onFail(code, reason),
65
+ onMessage: (opcode, data) => this.#onMessage(opcode, data),
66
+ onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
67
+ onParserDrain: () => this.#onParserDrain(),
68
+ onSocketData: (chunk) => {
69
+ if (!this.#parser.write(chunk)) {
70
+ this.#handler.socket.pause()
71
+ }
72
+ },
73
+ onSocketError: (err) => {
74
+ this.#handler.readyState = states.CLOSING
75
+
76
+ if (channels.socketError.hasSubscribers) {
77
+ channels.socketError.publish(err)
78
+ }
79
+
80
+ this.#handler.socket.destroy()
81
+ },
82
+ onSocketClose: () => this.#onSocketClose(),
83
+
84
+ readyState: states.CONNECTING,
85
+ socket: null,
86
+ closeState: new Set(),
87
+ controller: null,
88
+ wasEverConnected: false
89
+ }
90
+
91
+ #url
92
+ #binaryType
93
+ /** @type {import('./receiver').ByteParser} */
94
+ #parser
95
+
47
96
  /**
48
97
  * @param {string} url
49
98
  * @param {string|string[]} protocols
@@ -56,51 +105,22 @@ class WebSocket extends EventTarget {
56
105
 
57
106
  const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
58
107
 
59
- url = webidl.converters.USVString(url, prefix, 'url')
108
+ url = webidl.converters.USVString(url)
60
109
  protocols = options.protocols
61
110
 
62
111
  // 1. Let baseURL be this's relevant settings object's API base URL.
63
112
  const baseURL = environmentSettingsObject.settingsObject.baseUrl
64
113
 
65
- // 1. Let urlRecord be the result of applying the URL parser to url with baseURL.
66
- let urlRecord
67
-
68
- try {
69
- urlRecord = new URL(url, baseURL)
70
- } catch (e) {
71
- // 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
72
- throw new DOMException(e, 'SyntaxError')
73
- }
74
-
75
- // 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws".
76
- if (urlRecord.protocol === 'http:') {
77
- urlRecord.protocol = 'ws:'
78
- } else if (urlRecord.protocol === 'https:') {
79
- // 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss".
80
- urlRecord.protocol = 'wss:'
81
- }
82
-
83
- // 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
84
- if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
85
- throw new DOMException(
86
- `Expected a ws: or wss: protocol, got ${urlRecord.protocol}`,
87
- 'SyntaxError'
88
- )
89
- }
114
+ // 2. Let urlRecord be the result of getting a URL record given url and baseURL.
115
+ const urlRecord = getURLRecord(url, baseURL)
90
116
 
91
- // 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError"
92
- // DOMException.
93
- if (urlRecord.hash || urlRecord.href.endsWith('#')) {
94
- throw new DOMException('Got fragment', 'SyntaxError')
95
- }
96
-
97
- // 8. If protocols is a string, set protocols to a sequence consisting
117
+ // 3. If protocols is a string, set protocols to a sequence consisting
98
118
  // of just that string.
99
119
  if (typeof protocols === 'string') {
100
120
  protocols = [protocols]
101
121
  }
102
122
 
103
- // 9. If any of the values in protocols occur more than once or otherwise
123
+ // 4. If any of the values in protocols occur more than once or otherwise
104
124
  // fail to match the requirements for elements that comprise the value
105
125
  // of `Sec-WebSocket-Protocol` fields as defined by The WebSocket
106
126
  // protocol, then throw a "SyntaxError" DOMException.
@@ -112,31 +132,27 @@ class WebSocket extends EventTarget {
112
132
  throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
113
133
  }
114
134
 
115
- // 10. Set this's url to urlRecord.
116
- this[kWebSocketURL] = new URL(urlRecord.href)
135
+ // 5. Set this's url to urlRecord.
136
+ this.#url = new URL(urlRecord.href)
117
137
 
118
- // 11. Let client be this's relevant settings object.
138
+ // 6. Let client be this's relevant settings object.
119
139
  const client = environmentSettingsObject.settingsObject
120
140
 
121
- // 12. Run this step in parallel:
122
-
123
- // 1. Establish a WebSocket connection given urlRecord, protocols,
124
- // and client.
125
- this[kController] = establishWebSocketConnection(
141
+ // 7. Run this step in parallel:
142
+ // 7.1. Establish a WebSocket connection given urlRecord, protocols,
143
+ // and client.
144
+ this.#handler.controller = establishWebSocketConnection(
126
145
  urlRecord,
127
146
  protocols,
128
147
  client,
129
- this,
130
- (response, extensions) => this.#onConnectionEstablished(response, extensions),
148
+ this.#handler,
131
149
  options
132
150
  )
133
151
 
134
152
  // Each WebSocket object has an associated ready state, which is a
135
153
  // number representing the state of the connection. Initially it must
136
154
  // be CONNECTING (0).
137
- this[kReadyState] = WebSocket.CONNECTING
138
-
139
- this[kSentClose] = sentCloseFrameState.NOT_SENT
155
+ this.#handler.readyState = WebSocket.CONNECTING
140
156
 
141
157
  // The extensions attribute must initially return the empty string.
142
158
 
@@ -144,7 +160,7 @@ class WebSocket extends EventTarget {
144
160
 
145
161
  // Each WebSocket object has an associated binary type, which is a
146
162
  // BinaryType. Initially it must be "blob".
147
- this[kBinaryType] = 'blob'
163
+ this.#binaryType = 'blob'
148
164
  }
149
165
 
150
166
  /**
@@ -162,37 +178,17 @@ class WebSocket extends EventTarget {
162
178
  }
163
179
 
164
180
  if (reason !== undefined) {
165
- reason = webidl.converters.USVString(reason, prefix, 'reason')
181
+ reason = webidl.converters.USVString(reason)
166
182
  }
167
183
 
168
- // 1. If code is present, but is neither an integer equal to 1000 nor an
169
- // integer in the range 3000 to 4999, inclusive, throw an
170
- // "InvalidAccessError" DOMException.
171
- if (code !== undefined) {
172
- if (code !== 1000 && (code < 3000 || code > 4999)) {
173
- throw new DOMException('invalid code', 'InvalidAccessError')
174
- }
175
- }
184
+ // 1. If code is the special value "missing", then set code to null.
185
+ code ??= null
176
186
 
177
- let reasonByteLength = 0
187
+ // 2. If reason is the special value "missing", then set reason to the empty string.
188
+ reason ??= ''
178
189
 
179
- // 2. If reason is present, then run these substeps:
180
- if (reason !== undefined) {
181
- // 1. Let reasonBytes be the result of encoding reason.
182
- // 2. If reasonBytes is longer than 123 bytes, then throw a
183
- // "SyntaxError" DOMException.
184
- reasonByteLength = Buffer.byteLength(reason)
185
-
186
- if (reasonByteLength > 123) {
187
- throw new DOMException(
188
- `Reason must be less than 123 bytes; received ${reasonByteLength}`,
189
- 'SyntaxError'
190
- )
191
- }
192
- }
193
-
194
- // 3. Run the first matching steps from the following list:
195
- closeWebSocketConnection(this, code, reason, reasonByteLength)
190
+ // 3. Close the WebSocket with this, code, and reason.
191
+ closeWebSocketConnection(this.#handler, code, reason, true)
196
192
  }
197
193
 
198
194
  /**
@@ -209,7 +205,7 @@ class WebSocket extends EventTarget {
209
205
 
210
206
  // 1. If this's ready state is CONNECTING, then throw an
211
207
  // "InvalidStateError" DOMException.
212
- if (isConnecting(this)) {
208
+ if (isConnecting(this.#handler.readyState)) {
213
209
  throw new DOMException('Sent before connected.', 'InvalidStateError')
214
210
  }
215
211
 
@@ -217,7 +213,7 @@ class WebSocket extends EventTarget {
217
213
  // https://datatracker.ietf.org/doc/html/rfc6455#section-6.1
218
214
  // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
219
215
 
220
- if (!isEstablished(this) || isClosing(this)) {
216
+ if (!isEstablished(this.#handler.readyState) || isClosing(this.#handler.readyState)) {
221
217
  return
222
218
  }
223
219
 
@@ -234,12 +230,12 @@ class WebSocket extends EventTarget {
234
230
  // the bufferedAmount attribute by the number of bytes needed to
235
231
  // express the argument as UTF-8.
236
232
 
237
- const length = Buffer.byteLength(data)
233
+ const buffer = Buffer.from(data)
238
234
 
239
- this.#bufferedAmount += length
240
- this.#sendQueue.add(data, () => {
241
- this.#bufferedAmount -= length
242
- }, sendHints.string)
235
+ this.#bufferedAmount += buffer.byteLength
236
+ this.#sendQueue.add(buffer, () => {
237
+ this.#bufferedAmount -= buffer.byteLength
238
+ }, sendHints.text)
243
239
  } else if (types.isArrayBuffer(data)) {
244
240
  // If the WebSocket connection is established, and the WebSocket
245
241
  // closing handshake has not yet started, then the user agent must
@@ -274,7 +270,7 @@ class WebSocket extends EventTarget {
274
270
  this.#sendQueue.add(data, () => {
275
271
  this.#bufferedAmount -= data.byteLength
276
272
  }, sendHints.typedArray)
277
- } else if (isBlobLike(data)) {
273
+ } else if (webidl.is.Blob(data)) {
278
274
  // If the WebSocket connection is established, and the WebSocket
279
275
  // closing handshake has not yet started, then the user agent must
280
276
  // send a WebSocket Message comprised of data using a binary frame
@@ -297,7 +293,7 @@ class WebSocket extends EventTarget {
297
293
  webidl.brandCheck(this, WebSocket)
298
294
 
299
295
  // The readyState getter steps are to return this's ready state.
300
- return this[kReadyState]
296
+ return this.#handler.readyState
301
297
  }
302
298
 
303
299
  get bufferedAmount () {
@@ -310,7 +306,7 @@ class WebSocket extends EventTarget {
310
306
  webidl.brandCheck(this, WebSocket)
311
307
 
312
308
  // The url getter steps are to return this's url, serialized.
313
- return URLSerializer(this[kWebSocketURL])
309
+ return URLSerializer(this.#url)
314
310
  }
315
311
 
316
312
  get extensions () {
@@ -412,16 +408,16 @@ class WebSocket extends EventTarget {
412
408
  get binaryType () {
413
409
  webidl.brandCheck(this, WebSocket)
414
410
 
415
- return this[kBinaryType]
411
+ return this.#binaryType
416
412
  }
417
413
 
418
414
  set binaryType (type) {
419
415
  webidl.brandCheck(this, WebSocket)
420
416
 
421
417
  if (type !== 'blob' && type !== 'arraybuffer') {
422
- this[kBinaryType] = 'blob'
418
+ this.#binaryType = 'blob'
423
419
  } else {
424
- this[kBinaryType] = type
420
+ this.#binaryType = type
425
421
  }
426
422
  }
427
423
 
@@ -431,19 +427,17 @@ class WebSocket extends EventTarget {
431
427
  #onConnectionEstablished (response, parsedExtensions) {
432
428
  // processResponse is called when the "response’s header list has been received and initialized."
433
429
  // once this happens, the connection is open
434
- this[kResponse] = response
435
-
436
- const parser = new ByteParser(this, parsedExtensions)
437
- parser.on('drain', onParserDrain)
438
- parser.on('error', onParserError.bind(this))
430
+ this.#handler.socket = response.socket
439
431
 
440
- response.socket.ws = this
441
- this[kByteParser] = parser
432
+ const parser = new ByteParser(this.#handler, parsedExtensions)
433
+ parser.on('drain', () => this.#handler.onParserDrain())
434
+ parser.on('error', (err) => this.#handler.onParserError(err))
442
435
 
436
+ this.#parser = parser
443
437
  this.#sendQueue = new SendQueue(response.socket)
444
438
 
445
439
  // 1. Change the ready state to OPEN (1).
446
- this[kReadyState] = states.OPEN
440
+ this.#handler.readyState = states.OPEN
447
441
 
448
442
  // 2. Change the extensions attribute’s value to the extensions in use, if
449
443
  // it is not the null value.
@@ -466,6 +460,131 @@ class WebSocket extends EventTarget {
466
460
  // 4. Fire an event named open at the WebSocket object.
467
461
  fireEvent('open', this)
468
462
  }
463
+
464
+ #onFail (code, reason) {
465
+ if (reason) {
466
+ // TODO: process.nextTick
467
+ fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
468
+ error: new Error(reason),
469
+ message: reason
470
+ })
471
+ }
472
+
473
+ if (!this.#handler.wasEverConnected) {
474
+ this.#handler.readyState = states.CLOSED
475
+
476
+ // If the WebSocket connection could not be established, it is also said
477
+ // that _The WebSocket Connection is Closed_, but not _cleanly_.
478
+ fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
479
+ wasClean: false, code, reason
480
+ })
481
+ }
482
+ }
483
+
484
+ #onMessage (type, data) {
485
+ // 1. If ready state is not OPEN (1), then return.
486
+ if (this.#handler.readyState !== states.OPEN) {
487
+ return
488
+ }
489
+
490
+ // 2. Let dataForEvent be determined by switching on type and binary type:
491
+ let dataForEvent
492
+
493
+ if (type === opcodes.TEXT) {
494
+ // -> type indicates that the data is Text
495
+ // a new DOMString containing data
496
+ try {
497
+ dataForEvent = utf8Decode(data)
498
+ } catch {
499
+ failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')
500
+ return
501
+ }
502
+ } else if (type === opcodes.BINARY) {
503
+ if (this.#binaryType === 'blob') {
504
+ // -> type indicates that the data is Binary and binary type is "blob"
505
+ // a new Blob object, created in the relevant Realm of the WebSocket
506
+ // object, that represents data as its raw data
507
+ dataForEvent = new Blob([data])
508
+ } else {
509
+ // -> type indicates that the data is Binary and binary type is "arraybuffer"
510
+ // a new ArrayBuffer object, created in the relevant Realm of the
511
+ // WebSocket object, whose contents are data
512
+ dataForEvent = toArrayBuffer(data)
513
+ }
514
+ }
515
+
516
+ // 3. Fire an event named message at the WebSocket object, using MessageEvent,
517
+ // with the origin attribute initialized to the serialization of the WebSocket
518
+ // object’s url's origin, and the data attribute initialized to dataForEvent.
519
+ fireEvent('message', this, createFastMessageEvent, {
520
+ origin: this.#url.origin,
521
+ data: dataForEvent
522
+ })
523
+ }
524
+
525
+ #onParserDrain () {
526
+ this.#handler.socket.resume()
527
+ }
528
+
529
+ /**
530
+ * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
531
+ * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
532
+ */
533
+ #onSocketClose () {
534
+ // If the TCP connection was closed after the
535
+ // WebSocket closing handshake was completed, the WebSocket connection
536
+ // is said to have been closed _cleanly_.
537
+ const wasClean =
538
+ this.#handler.closeState.has(sentCloseFrameState.SENT) &&
539
+ this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
540
+
541
+ let code = 1005
542
+ let reason = ''
543
+
544
+ const result = this.#parser.closingInfo
545
+
546
+ if (result && !result.error) {
547
+ code = result.code ?? 1005
548
+ reason = result.reason
549
+ } else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
550
+ // If _The WebSocket
551
+ // Connection is Closed_ and no Close control frame was received by the
552
+ // endpoint (such as could occur if the underlying transport connection
553
+ // is lost), _The WebSocket Connection Close Code_ is considered to be
554
+ // 1006.
555
+ code = 1006
556
+ }
557
+
558
+ // 1. Change the ready state to CLOSED (3).
559
+ this.#handler.readyState = states.CLOSED
560
+
561
+ // 2. If the user agent was required to fail the WebSocket
562
+ // connection, or if the WebSocket connection was closed
563
+ // after being flagged as full, fire an event named error
564
+ // at the WebSocket object.
565
+ // TODO
566
+
567
+ // 3. Fire an event named close at the WebSocket object,
568
+ // using CloseEvent, with the wasClean attribute
569
+ // initialized to true if the connection closed cleanly
570
+ // and false otherwise, the code attribute initialized to
571
+ // the WebSocket connection close code, and the reason
572
+ // attribute initialized to the result of applying UTF-8
573
+ // decode without BOM to the WebSocket connection close
574
+ // reason.
575
+ // TODO: process.nextTick
576
+ fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
577
+ wasClean, code, reason
578
+ })
579
+
580
+ if (channels.close.hasSubscribers) {
581
+ channels.close.publish({
582
+ websocket: this,
583
+ code,
584
+ reason
585
+ })
586
+ }
587
+ }
469
588
  }
470
589
 
471
590
  // https://websockets.spec.whatwg.org/#dom-websocket-connecting
@@ -514,7 +633,7 @@ webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
514
633
  )
515
634
 
516
635
  webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
517
- if (webidl.util.Type(V) === 'Object' && Symbol.iterator in V) {
636
+ if (webidl.util.Type(V) === webidl.util.Types.OBJECT && Symbol.iterator in V) {
518
637
  return webidl.converters['sequence<DOMString>'](V)
519
638
  }
520
639
 
@@ -540,7 +659,7 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
540
659
  ])
541
660
 
542
661
  webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) {
543
- if (webidl.util.Type(V) === 'Object' && !(Symbol.iterator in V)) {
662
+ if (webidl.util.Type(V) === webidl.util.Types.OBJECT && !(Symbol.iterator in V)) {
544
663
  return webidl.converters.WebSocketInit(V)
545
664
  }
546
665
 
@@ -548,39 +667,19 @@ webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = functio
548
667
  }
549
668
 
550
669
  webidl.converters.WebSocketSendData = function (V) {
551
- if (webidl.util.Type(V) === 'Object') {
552
- if (isBlobLike(V)) {
553
- return webidl.converters.Blob(V, { strict: false })
670
+ if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
671
+ if (webidl.is.Blob(V)) {
672
+ return V
554
673
  }
555
674
 
556
675
  if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
557
- return webidl.converters.BufferSource(V)
676
+ return V
558
677
  }
559
678
  }
560
679
 
561
680
  return webidl.converters.USVString(V)
562
681
  }
563
682
 
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
683
  module.exports = {
585
684
  WebSocket
586
685
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.20.0",
3
+ "version": "7.0.0-alpha.2",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -62,22 +62,22 @@
62
62
  "main": "index.js",
63
63
  "types": "index.d.ts",
64
64
  "scripts": {
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",
65
+ "build:node": "esbuild 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,44 +102,31 @@
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.19.50",
108
+ "@types/node": "^18.19.50",
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
+ "esbuild": "^0.24.0",
115
+ "eslint": "^9.9.0",
114
116
  "fast-check": "^3.17.1",
115
- "form-data": "^4.0.0",
116
- "formdata-node": "^6.0.3",
117
117
  "https-pem": "^3.0.0",
118
118
  "husky": "^9.0.7",
119
119
  "jest": "^29.0.2",
120
- "jsdom": "^24.0.0",
120
+ "neostandard": "^0.11.2",
121
121
  "node-forge": "^1.3.1",
122
- "pre-commit": "^1.2.2",
123
122
  "proxy": "^2.1.1",
124
- "snazzy": "^9.0.0",
125
- "standard": "^17.0.0",
126
- "tsd": "^0.31.0",
127
- "typescript": "^5.0.2",
123
+ "tsd": "^0.31.2",
124
+ "typescript": "^5.6.2",
128
125
  "ws": "^8.11.0"
129
126
  },
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'))