undici 7.0.0-alpha.1 → 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 (71) hide show
  1. package/README.md +2 -2
  2. package/docs/docs/api/Client.md +1 -1
  3. package/docs/docs/api/Debug.md +1 -1
  4. package/docs/docs/api/Dispatcher.md +53 -2
  5. package/docs/docs/api/MockAgent.md +2 -0
  6. package/docs/docs/api/MockPool.md +2 -1
  7. package/docs/docs/api/RetryAgent.md +1 -1
  8. package/docs/docs/api/RetryHandler.md +1 -1
  9. package/docs/docs/api/WebSocket.md +45 -3
  10. package/index.js +6 -2
  11. package/lib/api/abort-signal.js +2 -0
  12. package/lib/api/api-pipeline.js +4 -2
  13. package/lib/api/api-request.js +4 -2
  14. package/lib/api/api-stream.js +3 -1
  15. package/lib/api/api-upgrade.js +2 -2
  16. package/lib/api/readable.js +194 -41
  17. package/lib/api/util.js +2 -0
  18. package/lib/core/connect.js +49 -22
  19. package/lib/core/constants.js +11 -9
  20. package/lib/core/diagnostics.js +122 -128
  21. package/lib/core/request.js +4 -4
  22. package/lib/core/symbols.js +2 -0
  23. package/lib/core/tree.js +4 -2
  24. package/lib/core/util.js +220 -39
  25. package/lib/dispatcher/client-h1.js +299 -60
  26. package/lib/dispatcher/client-h2.js +1 -1
  27. package/lib/dispatcher/client.js +24 -7
  28. package/lib/dispatcher/fixed-queue.js +91 -49
  29. package/lib/dispatcher/pool-stats.js +2 -0
  30. package/lib/dispatcher/proxy-agent.js +3 -1
  31. package/lib/handler/redirect-handler.js +2 -2
  32. package/lib/handler/retry-handler.js +2 -2
  33. package/lib/interceptor/dns.js +346 -0
  34. package/lib/mock/mock-agent.js +5 -8
  35. package/lib/mock/mock-client.js +7 -2
  36. package/lib/mock/mock-errors.js +3 -1
  37. package/lib/mock/mock-interceptor.js +8 -6
  38. package/lib/mock/mock-pool.js +7 -2
  39. package/lib/mock/mock-symbols.js +2 -1
  40. package/lib/mock/mock-utils.js +33 -5
  41. package/lib/util/timers.js +50 -6
  42. package/lib/web/cache/cache.js +24 -21
  43. package/lib/web/cache/cachestorage.js +1 -1
  44. package/lib/web/cookies/index.js +6 -4
  45. package/lib/web/fetch/body.js +42 -34
  46. package/lib/web/fetch/constants.js +35 -26
  47. package/lib/web/fetch/formdata-parser.js +14 -3
  48. package/lib/web/fetch/formdata.js +40 -20
  49. package/lib/web/fetch/headers.js +116 -84
  50. package/lib/web/fetch/index.js +65 -59
  51. package/lib/web/fetch/request.js +130 -55
  52. package/lib/web/fetch/response.js +79 -36
  53. package/lib/web/fetch/util.js +104 -57
  54. package/lib/web/fetch/webidl.js +38 -14
  55. package/lib/web/websocket/connection.js +92 -15
  56. package/lib/web/websocket/constants.js +2 -3
  57. package/lib/web/websocket/events.js +4 -2
  58. package/lib/web/websocket/receiver.js +20 -26
  59. package/lib/web/websocket/stream/websocketerror.js +83 -0
  60. package/lib/web/websocket/stream/websocketstream.js +485 -0
  61. package/lib/web/websocket/util.js +115 -10
  62. package/lib/web/websocket/websocket.js +45 -170
  63. package/package.json +6 -6
  64. package/types/interceptors.d.ts +14 -0
  65. package/types/mock-agent.d.ts +3 -0
  66. package/types/readable.d.ts +10 -7
  67. package/types/webidl.d.ts +24 -4
  68. package/types/websocket.d.ts +33 -0
  69. package/lib/mock/pluralizer.js +0 -29
  70. package/lib/web/cache/symbols.js +0 -5
  71. package/lib/web/fetch/symbols.js +0 -8
@@ -1,12 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const { uid } = require('./constants')
4
- const { failWebsocketConnection, parseExtensions } = require('./util')
3
+ const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
4
+ const { failWebsocketConnection, parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
5
5
  const { channels } = require('../../core/diagnostics')
6
6
  const { makeRequest } = require('../fetch/request')
7
7
  const { fetching } = require('../fetch/index')
8
8
  const { Headers, getHeadersList } = require('../fetch/headers')
9
9
  const { getDecodeSplit } = require('../fetch/util')
10
+ const { WebsocketFrameSend } = require('./frame')
11
+ const assert = require('node:assert')
10
12
 
11
13
  /** @type {import('crypto')} */
12
14
  let crypto
@@ -94,10 +96,16 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
94
96
  useParallelQueue: true,
95
97
  dispatcher: options.dispatcher,
96
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
+
97
105
  // 1. If response is a network error or its status is not 101,
98
106
  // fail the WebSocket connection.
99
107
  if (response.type === 'error' || response.status !== 101) {
100
- failWebsocketConnection(handler, 'Received network error or non-101 status code.')
108
+ failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.')
101
109
  return
102
110
  }
103
111
 
@@ -106,7 +114,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
106
114
  // header list results in null, failure, or the empty byte
107
115
  // sequence, then fail the WebSocket connection.
108
116
  if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
109
- failWebsocketConnection(handler, 'Server did not respond with sent protocols.')
117
+ failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
110
118
  return
111
119
  }
112
120
 
@@ -121,7 +129,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
121
129
  // insensitive match for the value "websocket", the client MUST
122
130
  // _Fail the WebSocket Connection_.
123
131
  if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
124
- failWebsocketConnection(handler, 'Server did not set Upgrade header to "websocket".')
132
+ failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
125
133
  return
126
134
  }
127
135
 
@@ -130,7 +138,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
130
138
  // ASCII case-insensitive match for the value "Upgrade", the client
131
139
  // MUST _Fail the WebSocket Connection_.
132
140
  if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
133
- failWebsocketConnection(handler, 'Server did not set Connection header to "upgrade".')
141
+ failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
134
142
  return
135
143
  }
136
144
 
@@ -144,7 +152,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
144
152
  const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
145
153
  const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
146
154
  if (secWSAccept !== digest) {
147
- failWebsocketConnection(handler, 'Incorrect hash received in Sec-WebSocket-Accept header.')
155
+ failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
148
156
  return
149
157
  }
150
158
 
@@ -162,7 +170,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
162
170
  extensions = parseExtensions(secExtension)
163
171
 
164
172
  if (!extensions.has('permessage-deflate')) {
165
- failWebsocketConnection(handler, 'Sec-WebSocket-Extensions header does not match.')
173
+ failWebsocketConnection(handler, 1002, 'Sec-WebSocket-Extensions header does not match.')
166
174
  return
167
175
  }
168
176
  }
@@ -183,7 +191,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
183
191
  // the selected subprotocol values in its response for the connection to
184
192
  // be established.
185
193
  if (!requestProtocols.includes(secProtocol)) {
186
- failWebsocketConnection(handler, 'Protocol was not set in the opening handshake.')
194
+ failWebsocketConnection(handler, 1002, 'Protocol was not set in the opening handshake.')
187
195
  return
188
196
  }
189
197
  }
@@ -200,6 +208,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
200
208
  })
201
209
  }
202
210
 
211
+ handler.wasEverConnected = true
203
212
  handler.onConnectionEstablished(response, extensions)
204
213
  }
205
214
  })
@@ -208,13 +217,81 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
208
217
  }
209
218
 
210
219
  /**
211
- * @param {import('./websocket').Handler} handler
212
- * @param {number} code
213
- * @param {any} reason
214
- * @param {number} reasonByteLength
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='']
215
224
  */
216
- function closeWebSocketConnection (handler, code, reason, reasonByteLength) {
217
- handler.onClose(code, reason, reasonByteLength)
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)) {
241
+ // Do nothing.
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.
250
+
251
+ const frame = new WebsocketFrameSend()
252
+
253
+ // If neither code nor reason is present, the WebSocket Close
254
+ // message must not have a body.
255
+
256
+ // If code is present, then the status code to use in the
257
+ // WebSocket Close message must be the integer given by code.
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) {
270
+ frame.frameData = Buffer.allocUnsafe(2)
271
+ frame.frameData.writeUInt16BE(code, 0)
272
+ } else if (code !== null && reason !== null) {
273
+ // If reason is also present, then reasonBytes must be
274
+ // provided in the Close message after the status code.
275
+ frame.frameData = Buffer.allocUnsafe(2 + Buffer.byteLength(reason))
276
+ frame.frameData.writeUInt16BE(code, 0)
277
+ // the body MAY contain UTF-8-encoded data with value /reason/
278
+ frame.frameData.write(reason, 2, 'utf-8')
279
+ } else {
280
+ frame.frameData = emptyBuffer
281
+ }
282
+
283
+ object.socket.write(frame.createFrame(opcodes.CLOSE))
284
+
285
+ object.closeState.add(sentCloseFrameState.SENT)
286
+
287
+ // Upon either sending or receiving a Close control frame, it is said
288
+ // that _The WebSocket Closing Handshake is Started_ and that the
289
+ // WebSocket connection is in the CLOSING state.
290
+ object.readyState = states.CLOSING
291
+ } else {
292
+ // Set object’s ready state to CLOSING (2).
293
+ object.readyState = states.CLOSING
294
+ }
218
295
  }
219
296
 
220
297
  module.exports = {
@@ -21,9 +21,8 @@ const states = {
21
21
  }
22
22
 
23
23
  const sentCloseFrameState = {
24
- NOT_SENT: 0,
25
- PROCESSING: 1,
26
- SENT: 2
24
+ SENT: 1,
25
+ RECEIVED: 2
27
26
  }
28
27
 
29
28
  const opcodes = {
@@ -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
@@ -215,7 +214,10 @@ Object.defineProperties(ErrorEvent.prototype, {
215
214
  error: kEnumerableProperty
216
215
  })
217
216
 
218
- webidl.converters.MessagePort = webidl.interfaceConverter(MessagePort)
217
+ webidl.converters.MessagePort = webidl.interfaceConverter(
218
+ webidl.is.MessagePort,
219
+ 'MessagePort'
220
+ )
219
221
 
220
222
  webidl.converters['sequence<MessagePort>'] = webidl.sequenceConverter(
221
223
  webidl.converters.MessagePort
@@ -15,7 +15,6 @@ const {
15
15
  isContinuationFrame
16
16
  } = require('./util')
17
17
  const { WebsocketFrameSend } = require('./frame')
18
- const { closeWebSocketConnection } = require('./connection')
19
18
  const { PerMessageDeflate } = require('./permessage-deflate')
20
19
 
21
20
  // This code was influenced by ws released under the MIT license.
@@ -88,12 +87,12 @@ class ByteParser extends Writable {
88
87
  const rsv3 = buffer[0] & 0x10
89
88
 
90
89
  if (!isValidOpcode(opcode)) {
91
- failWebsocketConnection(this.#handler, 'Invalid opcode received')
90
+ failWebsocketConnection(this.#handler, 1002, 'Invalid opcode received')
92
91
  return callback()
93
92
  }
94
93
 
95
94
  if (masked) {
96
- failWebsocketConnection(this.#handler, 'Frame cannot be masked')
95
+ failWebsocketConnection(this.#handler, 1002, 'Frame cannot be masked')
97
96
  return callback()
98
97
  }
99
98
 
@@ -107,43 +106,43 @@ class ByteParser extends Writable {
107
106
  // WebSocket connection where a PMCE is in use, this bit indicates
108
107
  // whether a message is compressed or not.
109
108
  if (rsv1 !== 0 && !this.#extensions.has('permessage-deflate')) {
110
- failWebsocketConnection(this.#handler, 'Expected RSV1 to be clear.')
109
+ failWebsocketConnection(this.#handler, 1002, 'Expected RSV1 to be clear.')
111
110
  return
112
111
  }
113
112
 
114
113
  if (rsv2 !== 0 || rsv3 !== 0) {
115
- failWebsocketConnection(this.#handler, 'RSV1, RSV2, RSV3 must be clear')
114
+ failWebsocketConnection(this.#handler, 1002, 'RSV1, RSV2, RSV3 must be clear')
116
115
  return
117
116
  }
118
117
 
119
118
  if (fragmented && !isTextBinaryFrame(opcode)) {
120
119
  // Only text and binary frames can be fragmented
121
- failWebsocketConnection(this.#handler, 'Invalid frame type was fragmented.')
120
+ failWebsocketConnection(this.#handler, 1002, 'Invalid frame type was fragmented.')
122
121
  return
123
122
  }
124
123
 
125
124
  // If we are already parsing a text/binary frame and do not receive either
126
125
  // a continuation frame or close frame, fail the connection.
127
126
  if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) {
128
- failWebsocketConnection(this.#handler, 'Expected continuation frame')
127
+ failWebsocketConnection(this.#handler, 1002, 'Expected continuation frame')
129
128
  return
130
129
  }
131
130
 
132
131
  if (this.#info.fragmented && fragmented) {
133
132
  // A fragmented frame can't be fragmented itself
134
- failWebsocketConnection(this.#handler, 'Fragmented frame exceeded 125 bytes.')
133
+ failWebsocketConnection(this.#handler, 1002, 'Fragmented frame exceeded 125 bytes.')
135
134
  return
136
135
  }
137
136
 
138
137
  // "All control frames MUST have a payload length of 125 bytes or less
139
138
  // and MUST NOT be fragmented."
140
139
  if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) {
141
- failWebsocketConnection(this.#handler, 'Control frame either too large or fragmented')
140
+ failWebsocketConnection(this.#handler, 1002, 'Control frame either too large or fragmented')
142
141
  return
143
142
  }
144
143
 
145
144
  if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) {
146
- failWebsocketConnection(this.#handler, 'Unexpected continuation frame')
145
+ failWebsocketConnection(this.#handler, 1002, 'Unexpected continuation frame')
147
146
  return
148
147
  }
149
148
 
@@ -189,7 +188,7 @@ class ByteParser extends Writable {
189
188
  // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
190
189
  // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
191
190
  if (upper > 2 ** 31 - 1) {
192
- failWebsocketConnection(this.#handler, 'Received payload length > 2^31 bytes.')
191
+ failWebsocketConnection(this.#handler, 1009, 'Received payload length > 2^31 bytes.')
193
192
  return
194
193
  }
195
194
 
@@ -225,7 +224,7 @@ class ByteParser extends Writable {
225
224
  } else {
226
225
  this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
227
226
  if (error) {
228
- closeWebSocketConnection(this.#handler, 1007, error.message, error.message.length)
227
+ failWebsocketConnection(this.#handler, 1007, error.message)
229
228
  return
230
229
  }
231
230
 
@@ -341,7 +340,7 @@ class ByteParser extends Writable {
341
340
 
342
341
  if (opcode === opcodes.CLOSE) {
343
342
  if (payloadLength === 1) {
344
- failWebsocketConnection(this.#handler, 'Received close frame with a 1-byte body.')
343
+ failWebsocketConnection(this.#handler, 1002, 'Received close frame with a 1-byte body.')
345
344
  return false
346
345
  }
347
346
 
@@ -350,12 +349,13 @@ class ByteParser extends Writable {
350
349
  if (this.#info.closeInfo.error) {
351
350
  const { code, reason } = this.#info.closeInfo
352
351
 
353
- closeWebSocketConnection(this.#handler, code, reason, reason.length)
354
- failWebsocketConnection(this.#handler, reason)
352
+ failWebsocketConnection(this.#handler, code, reason)
355
353
  return false
356
354
  }
357
355
 
358
- if (this.#handler.closeState !== sentCloseFrameState.SENT) {
356
+ // Upon receiving such a frame, the other peer sends a
357
+ // Close frame in response, if it hasn't already sent one.
358
+ if (!this.#handler.closeState.has(sentCloseFrameState.SENT) && !this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
359
359
  // If an endpoint receives a Close frame and did not previously send a
360
360
  // Close frame, the endpoint MUST send a Close frame in response. (When
361
361
  // sending a Close frame in response, the endpoint typically echos the
@@ -367,21 +367,15 @@ class ByteParser extends Writable {
367
367
  }
368
368
  const closeFrame = new WebsocketFrameSend(body)
369
369
 
370
- this.#handler.socket.write(
371
- closeFrame.createFrame(opcodes.CLOSE),
372
- (err) => {
373
- if (!err) {
374
- this.#handler.closeState = sentCloseFrameState.SENT
375
- }
376
- }
377
- )
370
+ this.#handler.socket.write(closeFrame.createFrame(opcodes.CLOSE))
371
+ this.#handler.closeState.add(sentCloseFrameState.SENT)
378
372
  }
379
373
 
380
374
  // Upon either sending or receiving a Close control frame, it is said
381
375
  // that _The WebSocket Closing Handshake is Started_ and that the
382
376
  // WebSocket connection is in the CLOSING state.
383
377
  this.#handler.readyState = states.CLOSING
384
- this.#handler.receivedClose = true
378
+ this.#handler.closeState.add(sentCloseFrameState.RECEIVED)
385
379
 
386
380
  return false
387
381
  } else if (opcode === opcodes.PING) {
@@ -390,7 +384,7 @@ class ByteParser extends Writable {
390
384
  // A Pong frame sent in response to a Ping frame must have identical
391
385
  // "Application data"
392
386
 
393
- if (!this.#handler.receivedClose) {
387
+ if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
394
388
  const frame = new WebsocketFrameSend(body)
395
389
 
396
390
  this.#handler.socket.write(frame.createFrame(opcodes.PONG))
@@ -0,0 +1,83 @@
1
+ 'use strict'
2
+
3
+ const { webidl } = require('../../fetch/webidl')
4
+ const { validateCloseCodeAndReason } = require('../util')
5
+ const { kConstruct } = require('../../../core/symbols')
6
+ const { kEnumerableProperty } = require('../../../core/util')
7
+
8
+ class WebSocketError extends DOMException {
9
+ #closeCode
10
+ #reason
11
+
12
+ constructor (message = '', init = undefined) {
13
+ message = webidl.converters.DOMString(message, 'WebSocketError', 'message')
14
+
15
+ // 1. Set this 's name to " WebSocketError ".
16
+ // 2. Set this 's message to message .
17
+ super(message, 'WebSocketError')
18
+
19
+ if (init === kConstruct) {
20
+ return
21
+ } else if (init !== null) {
22
+ init = webidl.converters.WebSocketCloseInfo(init)
23
+ }
24
+
25
+ // 3. Let code be init [" closeCode "] if it exists , or null otherwise.
26
+ let code = init.closeCode ?? null
27
+
28
+ // 4. Let reason be init [" reason "] if it exists , or the empty string otherwise.
29
+ const reason = init.reason ?? ''
30
+
31
+ // 5. Validate close code and reason with code and reason .
32
+ validateCloseCodeAndReason(code, reason)
33
+
34
+ // 6. If reason is non-empty, but code is not set, then set code to 1000 ("Normal Closure").
35
+ if (reason.length !== 0 && code === null) {
36
+ code = 1000
37
+ }
38
+
39
+ // 7. Set this 's closeCode to code .
40
+ this.#closeCode = code
41
+
42
+ // 8. Set this 's reason to reason .
43
+ this.#reason = reason
44
+ }
45
+
46
+ get closeCode () {
47
+ return this.#closeCode
48
+ }
49
+
50
+ get reason () {
51
+ return this.#reason
52
+ }
53
+
54
+ /**
55
+ * @param {string} message
56
+ * @param {number|null} code
57
+ * @param {string} reason
58
+ */
59
+ static createUnvalidatedWebSocketError (message, code, reason) {
60
+ const error = new WebSocketError(message, kConstruct)
61
+ error.#closeCode = code
62
+ error.#reason = reason
63
+ return error
64
+ }
65
+ }
66
+
67
+ const { createUnvalidatedWebSocketError } = WebSocketError
68
+ delete WebSocketError.createUnvalidatedWebSocketError
69
+
70
+ Object.defineProperties(WebSocketError.prototype, {
71
+ closeCode: kEnumerableProperty,
72
+ reason: kEnumerableProperty,
73
+ [Symbol.toStringTag]: {
74
+ value: 'WebSocketError',
75
+ writable: false,
76
+ enumerable: false,
77
+ configurable: true
78
+ }
79
+ })
80
+
81
+ webidl.is.WebSocketError = webidl.util.MakeTypeAssertion(WebSocketError.prototype)
82
+
83
+ module.exports = { WebSocketError, createUnvalidatedWebSocketError }