undici 6.21.0 → 7.0.0-alpha.10

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 (156) hide show
  1. package/README.md +27 -46
  2. package/docs/docs/api/Agent.md +14 -17
  3. package/docs/docs/api/BalancedPool.md +16 -16
  4. package/docs/docs/api/CacheStore.md +131 -0
  5. package/docs/docs/api/Client.md +12 -14
  6. package/docs/docs/api/Debug.md +1 -1
  7. package/docs/docs/api/Dispatcher.md +98 -194
  8. package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
  9. package/docs/docs/api/MockAgent.md +5 -3
  10. package/docs/docs/api/MockClient.md +5 -5
  11. package/docs/docs/api/MockPool.md +4 -3
  12. package/docs/docs/api/Pool.md +15 -16
  13. package/docs/docs/api/PoolStats.md +1 -1
  14. package/docs/docs/api/ProxyAgent.md +3 -3
  15. package/docs/docs/api/RedirectHandler.md +1 -1
  16. package/docs/docs/api/RetryAgent.md +1 -1
  17. package/docs/docs/api/RetryHandler.md +4 -4
  18. package/docs/docs/api/WebSocket.md +46 -4
  19. package/docs/docs/api/api-lifecycle.md +11 -11
  20. package/docs/docs/best-practices/mocking-request.md +2 -2
  21. package/docs/docs/best-practices/proxy.md +1 -1
  22. package/index.d.ts +1 -1
  23. package/index.js +23 -7
  24. package/lib/api/abort-signal.js +2 -0
  25. package/lib/api/api-connect.js +3 -1
  26. package/lib/api/api-pipeline.js +7 -6
  27. package/lib/api/api-request.js +33 -48
  28. package/lib/api/api-stream.js +39 -50
  29. package/lib/api/api-upgrade.js +5 -3
  30. package/lib/api/readable.js +235 -62
  31. package/lib/api/util.js +2 -0
  32. package/lib/cache/memory-cache-store.js +177 -0
  33. package/lib/cache/sqlite-cache-store.js +446 -0
  34. package/lib/core/constants.js +35 -10
  35. package/lib/core/diagnostics.js +122 -128
  36. package/lib/core/errors.js +6 -6
  37. package/lib/core/request.js +13 -11
  38. package/lib/core/symbols.js +2 -1
  39. package/lib/core/tree.js +9 -1
  40. package/lib/core/util.js +237 -49
  41. package/lib/dispatcher/agent.js +3 -17
  42. package/lib/dispatcher/balanced-pool.js +5 -8
  43. package/lib/dispatcher/client-h1.js +379 -134
  44. package/lib/dispatcher/client-h2.js +173 -107
  45. package/lib/dispatcher/client.js +19 -32
  46. package/lib/dispatcher/dispatcher-base.js +6 -35
  47. package/lib/dispatcher/dispatcher.js +7 -24
  48. package/lib/dispatcher/fixed-queue.js +91 -49
  49. package/lib/dispatcher/pool-stats.js +2 -0
  50. package/lib/dispatcher/pool.js +3 -6
  51. package/lib/dispatcher/proxy-agent.js +3 -6
  52. package/lib/handler/cache-handler.js +393 -0
  53. package/lib/handler/cache-revalidation-handler.js +124 -0
  54. package/lib/handler/decorator-handler.js +27 -0
  55. package/lib/handler/redirect-handler.js +54 -59
  56. package/lib/handler/retry-handler.js +77 -109
  57. package/lib/handler/unwrap-handler.js +96 -0
  58. package/lib/handler/wrap-handler.js +98 -0
  59. package/lib/interceptor/cache.js +350 -0
  60. package/lib/interceptor/dns.js +375 -0
  61. package/lib/interceptor/dump.js +2 -2
  62. package/lib/interceptor/redirect.js +11 -14
  63. package/lib/interceptor/response-error.js +18 -7
  64. package/lib/llhttp/constants.d.ts +97 -0
  65. package/lib/llhttp/constants.js +412 -192
  66. package/lib/llhttp/constants.js.map +1 -0
  67. package/lib/llhttp/llhttp-wasm.js +11 -1
  68. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  69. package/lib/llhttp/utils.d.ts +2 -0
  70. package/lib/llhttp/utils.js +9 -9
  71. package/lib/llhttp/utils.js.map +1 -0
  72. package/lib/mock/mock-agent.js +5 -8
  73. package/lib/mock/mock-client.js +9 -4
  74. package/lib/mock/mock-errors.js +3 -1
  75. package/lib/mock/mock-interceptor.js +8 -6
  76. package/lib/mock/mock-pool.js +9 -4
  77. package/lib/mock/mock-symbols.js +3 -1
  78. package/lib/mock/mock-utils.js +29 -5
  79. package/lib/util/cache.js +360 -0
  80. package/lib/web/cache/cache.js +24 -21
  81. package/lib/web/cache/cachestorage.js +1 -1
  82. package/lib/web/cookies/index.js +29 -14
  83. package/lib/web/cookies/parse.js +8 -3
  84. package/lib/web/eventsource/eventsource-stream.js +9 -8
  85. package/lib/web/eventsource/eventsource.js +10 -6
  86. package/lib/web/fetch/body.js +43 -41
  87. package/lib/web/fetch/constants.js +12 -5
  88. package/lib/web/fetch/data-url.js +3 -3
  89. package/lib/web/fetch/formdata-parser.js +72 -45
  90. package/lib/web/fetch/formdata.js +65 -54
  91. package/lib/web/fetch/headers.js +118 -86
  92. package/lib/web/fetch/index.js +58 -67
  93. package/lib/web/fetch/request.js +136 -77
  94. package/lib/web/fetch/response.js +87 -56
  95. package/lib/web/fetch/util.js +259 -109
  96. package/lib/web/fetch/webidl.js +113 -68
  97. package/lib/web/websocket/connection.js +76 -147
  98. package/lib/web/websocket/constants.js +70 -10
  99. package/lib/web/websocket/events.js +4 -2
  100. package/lib/web/websocket/frame.js +45 -3
  101. package/lib/web/websocket/receiver.js +29 -33
  102. package/lib/web/websocket/sender.js +18 -13
  103. package/lib/web/websocket/stream/websocketerror.js +83 -0
  104. package/lib/web/websocket/stream/websocketstream.js +485 -0
  105. package/lib/web/websocket/util.js +128 -77
  106. package/lib/web/websocket/websocket.js +234 -135
  107. package/package.json +24 -36
  108. package/scripts/strip-comments.js +3 -1
  109. package/types/agent.d.ts +7 -7
  110. package/types/api.d.ts +24 -24
  111. package/types/balanced-pool.d.ts +11 -11
  112. package/types/cache-interceptor.d.ts +172 -0
  113. package/types/client.d.ts +11 -12
  114. package/types/cookies.d.ts +2 -0
  115. package/types/diagnostics-channel.d.ts +10 -10
  116. package/types/dispatcher.d.ts +113 -90
  117. package/types/env-http-proxy-agent.d.ts +2 -2
  118. package/types/errors.d.ts +53 -47
  119. package/types/fetch.d.ts +17 -16
  120. package/types/formdata.d.ts +7 -7
  121. package/types/global-dispatcher.d.ts +4 -4
  122. package/types/global-origin.d.ts +5 -5
  123. package/types/handlers.d.ts +7 -7
  124. package/types/header.d.ts +157 -1
  125. package/types/index.d.ts +44 -46
  126. package/types/interceptors.d.ts +25 -8
  127. package/types/mock-agent.d.ts +21 -18
  128. package/types/mock-client.d.ts +4 -4
  129. package/types/mock-errors.d.ts +3 -3
  130. package/types/mock-interceptor.d.ts +19 -19
  131. package/types/mock-pool.d.ts +4 -4
  132. package/types/patch.d.ts +0 -4
  133. package/types/pool-stats.d.ts +8 -8
  134. package/types/pool.d.ts +12 -12
  135. package/types/proxy-agent.d.ts +4 -4
  136. package/types/readable.d.ts +18 -15
  137. package/types/retry-agent.d.ts +1 -1
  138. package/types/retry-handler.d.ts +10 -10
  139. package/types/util.d.ts +3 -3
  140. package/types/utility.d.ts +7 -0
  141. package/types/webidl.d.ts +44 -6
  142. package/types/websocket.d.ts +34 -1
  143. package/docs/docs/api/DispatchInterceptor.md +0 -60
  144. package/lib/interceptor/redirect-interceptor.js +0 -21
  145. package/lib/mock/pluralizer.js +0 -29
  146. package/lib/web/cache/symbols.js +0 -5
  147. package/lib/web/fetch/file.js +0 -126
  148. package/lib/web/fetch/symbols.js +0 -9
  149. package/lib/web/fileapi/encoding.js +0 -290
  150. package/lib/web/fileapi/filereader.js +0 -344
  151. package/lib/web/fileapi/progressevent.js +0 -78
  152. package/lib/web/fileapi/symbols.js +0 -10
  153. package/lib/web/fileapi/util.js +0 -391
  154. package/lib/web/websocket/symbols.js +0 -12
  155. package/types/file.d.ts +0 -39
  156. package/types/filereader.d.ts +0 -54
@@ -1,21 +1,14 @@
1
1
  'use strict'
2
2
 
3
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')
4
+ const { failWebsocketConnection, parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = 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
10
  const { WebsocketFrameSend } = require('./frame')
11
+ const assert = require('node:assert')
19
12
 
20
13
  /** @type {import('crypto')} */
21
14
  let crypto
@@ -30,11 +23,10 @@ try {
30
23
  * @see https://websockets.spec.whatwg.org/#concept-websocket-establish
31
24
  * @param {URL} url
32
25
  * @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
26
+ * @param {import('./websocket').Handler} handler
27
+ * @param {Partial<import('../../../types/websocket').WebSocketInit>} options
36
28
  */
37
- function establishWebSocketConnection (url, protocols, client, ws, onEstablish, options) {
29
+ function establishWebSocketConnection (url, protocols, client, handler, options) {
38
30
  // 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s
39
31
  // scheme is "ws", and to "https" otherwise.
40
32
  const requestURL = url
@@ -75,17 +67,17 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
75
67
 
76
68
  // 6. Append (`Sec-WebSocket-Key`, keyValue) to request’s
77
69
  // header list.
78
- request.headersList.append('sec-websocket-key', keyValue)
70
+ request.headersList.append('sec-websocket-key', keyValue, true)
79
71
 
80
72
  // 7. Append (`Sec-WebSocket-Version`, `13`) to request’s
81
73
  // header list.
82
- request.headersList.append('sec-websocket-version', '13')
74
+ request.headersList.append('sec-websocket-version', '13', true)
83
75
 
84
76
  // 8. For each protocol in protocols, combine
85
77
  // (`Sec-WebSocket-Protocol`, protocol) in request’s header
86
78
  // list.
87
79
  for (const protocol of protocols) {
88
- request.headersList.append('sec-websocket-protocol', protocol)
80
+ request.headersList.append('sec-websocket-protocol', protocol, true)
89
81
  }
90
82
 
91
83
  // 9. Let permessageDeflate be a user-agent defined
@@ -95,7 +87,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
95
87
 
96
88
  // 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to
97
89
  // request’s header list.
98
- request.headersList.append('sec-websocket-extensions', permessageDeflate)
90
+ request.headersList.append('sec-websocket-extensions', permessageDeflate, true)
99
91
 
100
92
  // 11. Fetch request with useParallelQueue set to true, and
101
93
  // processResponse given response being these steps:
@@ -104,10 +96,16 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
104
96
  useParallelQueue: true,
105
97
  dispatcher: options.dispatcher,
106
98
  processResponse (response) {
99
+ if (response.type === 'error') {
100
+ // If the WebSocket connection could not be established, it is also said
101
+ // that _The WebSocket Connection is Closed_, but not _cleanly_.
102
+ handler.readyState = states.CLOSED
103
+ }
104
+
107
105
  // 1. If response is a network error or its status is not 101,
108
106
  // fail the WebSocket connection.
109
107
  if (response.type === 'error' || response.status !== 101) {
110
- failWebsocketConnection(ws, 'Received network error or non-101 status code.')
108
+ failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.')
111
109
  return
112
110
  }
113
111
 
@@ -116,7 +114,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
116
114
  // header list results in null, failure, or the empty byte
117
115
  // sequence, then fail the WebSocket connection.
118
116
  if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
119
- failWebsocketConnection(ws, 'Server did not respond with sent protocols.')
117
+ failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
120
118
  return
121
119
  }
122
120
 
@@ -131,7 +129,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
131
129
  // insensitive match for the value "websocket", the client MUST
132
130
  // _Fail the WebSocket Connection_.
133
131
  if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
134
- failWebsocketConnection(ws, 'Server did not set Upgrade header to "websocket".')
132
+ failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
135
133
  return
136
134
  }
137
135
 
@@ -140,7 +138,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
140
138
  // ASCII case-insensitive match for the value "Upgrade", the client
141
139
  // MUST _Fail the WebSocket Connection_.
142
140
  if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
143
- failWebsocketConnection(ws, 'Server did not set Connection header to "upgrade".')
141
+ failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
144
142
  return
145
143
  }
146
144
 
@@ -154,7 +152,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
154
152
  const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
155
153
  const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
156
154
  if (secWSAccept !== digest) {
157
- failWebsocketConnection(ws, 'Incorrect hash received in Sec-WebSocket-Accept header.')
155
+ failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
158
156
  return
159
157
  }
160
158
 
@@ -172,7 +170,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
172
170
  extensions = parseExtensions(secExtension)
173
171
 
174
172
  if (!extensions.has('permessage-deflate')) {
175
- failWebsocketConnection(ws, 'Sec-WebSocket-Extensions header does not match.')
173
+ failWebsocketConnection(handler, 1002, 'Sec-WebSocket-Extensions header does not match.')
176
174
  return
177
175
  }
178
176
  }
@@ -193,14 +191,14 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
193
191
  // the selected subprotocol values in its response for the connection to
194
192
  // be established.
195
193
  if (!requestProtocols.includes(secProtocol)) {
196
- failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
194
+ failWebsocketConnection(handler, 1002, 'Protocol was not set in the opening handshake.')
197
195
  return
198
196
  }
199
197
  }
200
198
 
201
- response.socket.on('data', onSocketData)
202
- response.socket.on('close', onSocketClose)
203
- response.socket.on('error', onSocketError)
199
+ response.socket.on('data', handler.onSocketData)
200
+ response.socket.on('close', handler.onSocketClose)
201
+ response.socket.on('error', handler.onSocketError)
204
202
 
205
203
  if (channels.open.hasSubscribers) {
206
204
  channels.open.publish({
@@ -210,35 +208,45 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
210
208
  })
211
209
  }
212
210
 
213
- onEstablish(response, extensions)
211
+ handler.wasEverConnected = true
212
+ handler.onConnectionEstablished(response, extensions)
214
213
  }
215
214
  })
216
215
 
217
216
  return controller
218
217
  }
219
218
 
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)
219
+ /**
220
+ * @see https://whatpr.org/websockets/48.html#close-the-websocket
221
+ * @param {import('./websocket').Handler} object
222
+ * @param {number} [code=null]
223
+ * @param {string} [reason='']
224
+ */
225
+ function closeWebSocketConnection (object, code, reason, validate = false) {
226
+ // 1. If code was not supplied, let code be null.
227
+ code ??= null
228
+
229
+ // 2. If reason was not supplied, let reason be the empty string.
230
+ reason ??= ''
231
+
232
+ // 3. Validate close code and reason with code and reason.
233
+ if (validate) validateCloseCodeAndReason(code, reason)
234
+
235
+ // 4. Run the first matching steps from the following list:
236
+ // - If object’s ready state is CLOSING (2) or CLOSED (3)
237
+ // - If the WebSocket connection is not yet established [WSP]
238
+ // - If the WebSocket closing handshake has not yet been started [WSP]
239
+ // - Otherwise
240
+ if (isClosed(object.readyState) || isClosing(object.readyState)) {
223
241
  // 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
+ } else if (!isEstablished(object.readyState)) {
243
+ // Fail the WebSocket connection and set object’s ready state to CLOSING (2). [WSP]
244
+ failWebsocketConnection(object)
245
+ object.readyState = states.CLOSING
246
+ } else if (!object.closeState.has(sentCloseFrameState.SENT) && !object.closeState.has(sentCloseFrameState.RECEIVED)) {
247
+ // Upon either sending or receiving a Close control frame, it is said
248
+ // that _The WebSocket Closing Handshake is Started_ and that the
249
+ // WebSocket connection is in the CLOSING state.
242
250
 
243
251
  const frame = new WebsocketFrameSend()
244
252
 
@@ -247,13 +255,24 @@ function closeWebSocketConnection (ws, code, reason, reasonByteLength) {
247
255
 
248
256
  // If code is present, then the status code to use in the
249
257
  // WebSocket Close message must be the integer given by code.
250
- if (code !== undefined && reason === undefined) {
258
+ // If code is null and reason is the empty string, the WebSocket Close frame must not have a body.
259
+ // If reason is non-empty but code is null, then set code to 1000 ("Normal Closure").
260
+ if (reason.length !== 0 && code === null) {
261
+ code = 1000
262
+ }
263
+
264
+ // If code is set, then the status code to use in the WebSocket Close frame must be the integer given by code.
265
+ assert(code === null || Number.isInteger(code))
266
+
267
+ if (code === null && reason.length === 0) {
268
+ frame.frameData = emptyBuffer
269
+ } else if (code !== null && reason === null) {
251
270
  frame.frameData = Buffer.allocUnsafe(2)
252
271
  frame.frameData.writeUInt16BE(code, 0)
253
- } else if (code !== undefined && reason !== undefined) {
272
+ } else if (code !== null && reason !== null) {
254
273
  // If reason is also present, then reasonBytes must be
255
274
  // provided in the Close message after the status code.
256
- frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
275
+ frame.frameData = Buffer.allocUnsafe(2 + Buffer.byteLength(reason))
257
276
  frame.frameData.writeUInt16BE(code, 0)
258
277
  // the body MAY contain UTF-8-encoded data with value /reason/
259
278
  frame.frameData.write(reason, 2, 'utf-8')
@@ -261,108 +280,18 @@ function closeWebSocketConnection (ws, code, reason, reasonByteLength) {
261
280
  frame.frameData = emptyBuffer
262
281
  }
263
282
 
264
- /** @type {import('stream').Duplex} */
265
- const socket = ws[kResponse].socket
266
-
267
- socket.write(frame.createFrame(opcodes.CLOSE))
283
+ object.socket.write(frame.createFrame(opcodes.CLOSE))
268
284
 
269
- ws[kSentClose] = sentCloseFrameState.SENT
285
+ object.closeState.add(sentCloseFrameState.SENT)
270
286
 
271
287
  // Upon either sending or receiving a Close control frame, it is said
272
288
  // that _The WebSocket Closing Handshake is Started_ and that the
273
289
  // WebSocket connection is in the CLOSING state.
274
- ws[kReadyState] = states.CLOSING
290
+ object.readyState = states.CLOSING
275
291
  } 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
- /**
292
- * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
293
- * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
294
- */
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)
292
+ // Set object’s ready state to CLOSING (2).
293
+ object.readyState = states.CLOSING
363
294
  }
364
-
365
- this.destroy()
366
295
  }
367
296
 
368
297
  module.exports = {
@@ -1,18 +1,32 @@
1
1
  'use strict'
2
2
 
3
- // This is a Globally Unique Identifier unique used
4
- // to validate that the endpoint accepts websocket
5
- // connections.
6
- // See https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3
3
+ /**
4
+ * This is a Globally Unique Identifier unique used to validate that the
5
+ * endpoint accepts websocket connections.
6
+ * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3
7
+ * @type {'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'}
8
+ */
7
9
  const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
8
10
 
9
- /** @type {PropertyDescriptor} */
11
+ /**
12
+ * @type {PropertyDescriptor}
13
+ */
10
14
  const staticPropertyDescriptors = {
11
15
  enumerable: true,
12
16
  writable: false,
13
17
  configurable: false
14
18
  }
15
19
 
20
+ /**
21
+ * The states of the WebSocket connection.
22
+ *
23
+ * @readonly
24
+ * @enum
25
+ * @property {0} CONNECTING
26
+ * @property {1} OPEN
27
+ * @property {2} CLOSING
28
+ * @property {3} CLOSED
29
+ */
16
30
  const states = {
17
31
  CONNECTING: 0,
18
32
  OPEN: 1,
@@ -20,12 +34,31 @@ const states = {
20
34
  CLOSED: 3
21
35
  }
22
36
 
37
+ /**
38
+ * @readonly
39
+ * @enum
40
+ * @property {0} NOT_SENT
41
+ * @property {1} PROCESSING
42
+ * @property {2} SENT
43
+ */
23
44
  const sentCloseFrameState = {
24
- NOT_SENT: 0,
25
- PROCESSING: 1,
26
- SENT: 2
45
+ SENT: 1,
46
+ RECEIVED: 2
27
47
  }
28
48
 
49
+ /**
50
+ * The WebSocket opcodes.
51
+ *
52
+ * @readonly
53
+ * @enum
54
+ * @property {0x0} CONTINUATION
55
+ * @property {0x1} TEXT
56
+ * @property {0x2} BINARY
57
+ * @property {0x8} CLOSE
58
+ * @property {0x9} PING
59
+ * @property {0xA} PONG
60
+ * @see https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
61
+ */
29
62
  const opcodes = {
30
63
  CONTINUATION: 0x0,
31
64
  TEXT: 0x1,
@@ -35,8 +68,23 @@ const opcodes = {
35
68
  PONG: 0xA
36
69
  }
37
70
 
38
- const maxUnsigned16Bit = 2 ** 16 - 1 // 65535
71
+ /**
72
+ * The maximum value for an unsigned 16-bit integer.
73
+ *
74
+ * @type {65535} 2 ** 16 - 1
75
+ */
76
+ const maxUnsigned16Bit = 65535
39
77
 
78
+ /**
79
+ * The states of the parser.
80
+ *
81
+ * @readonly
82
+ * @enum
83
+ * @property {0} INFO
84
+ * @property {2} PAYLOADLENGTH_16
85
+ * @property {3} PAYLOADLENGTH_64
86
+ * @property {4} READ_DATA
87
+ */
40
88
  const parserStates = {
41
89
  INFO: 0,
42
90
  PAYLOADLENGTH_16: 2,
@@ -44,10 +92,22 @@ const parserStates = {
44
92
  READ_DATA: 4
45
93
  }
46
94
 
95
+ /**
96
+ * An empty buffer.
97
+ *
98
+ * @type {Buffer}
99
+ */
47
100
  const emptyBuffer = Buffer.allocUnsafe(0)
48
101
 
102
+ /**
103
+ * @readonly
104
+ * @property {1} text
105
+ * @property {2} typedArray
106
+ * @property {3} arrayBuffer
107
+ * @property {4} blob
108
+ */
49
109
  const sendHints = {
50
- string: 1,
110
+ text: 1,
51
111
  typedArray: 2,
52
112
  arrayBuffer: 3,
53
113
  blob: 4
@@ -3,7 +3,6 @@
3
3
  const { webidl } = require('../fetch/webidl')
4
4
  const { kEnumerableProperty } = require('../../core/util')
5
5
  const { kConstruct } = require('../../core/symbols')
6
- const { MessagePort } = require('node:worker_threads')
7
6
 
8
7
  /**
9
8
  * @see https://html.spec.whatwg.org/multipage/comms.html#messageevent
@@ -219,7 +218,10 @@ Object.defineProperties(ErrorEvent.prototype, {
219
218
  error: kEnumerableProperty
220
219
  })
221
220
 
222
- webidl.converters.MessagePort = webidl.interfaceConverter(MessagePort)
221
+ webidl.converters.MessagePort = webidl.interfaceConverter(
222
+ webidl.is.MessagePort,
223
+ 'MessagePort'
224
+ )
223
225
 
224
226
  webidl.converters['sequence<MessagePort>'] = webidl.sequenceConverter(
225
227
  webidl.converters.MessagePort
@@ -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 = {