undici 6.14.1 → 6.16.0
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.
- package/docs/docs/api/Util.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index-fetch.js +12 -1
- package/lib/api/api-request.js +40 -17
- package/lib/api/readable.js +6 -2
- package/lib/dispatcher/client-h2.js +2 -0
- package/lib/handler/retry-handler.js +21 -14
- package/lib/web/cache/cache.js +39 -26
- package/lib/web/cache/cachestorage.js +13 -7
- package/lib/web/cookies/index.js +16 -15
- package/lib/web/eventsource/eventsource.js +7 -6
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/dispatcher-weakref.js +3 -2
- package/lib/web/fetch/formdata.js +24 -18
- package/lib/web/fetch/headers.js +54 -21
- package/lib/web/fetch/index.js +38 -26
- package/lib/web/fetch/request.js +103 -73
- package/lib/web/fetch/response.js +33 -14
- package/lib/web/fetch/util.js +1 -1
- package/lib/web/fetch/webidl.js +75 -62
- package/lib/web/fileapi/filereader.js +5 -5
- package/lib/web/fileapi/progressevent.js +7 -7
- package/lib/web/websocket/connection.js +76 -7
- package/lib/web/websocket/events.js +48 -26
- package/lib/web/websocket/frame.js +30 -8
- package/lib/web/websocket/receiver.js +18 -4
- package/lib/web/websocket/util.js +16 -7
- package/lib/web/websocket/websocket.js +38 -88
- package/package.json +4 -4
- package/types/fetch.d.ts +2 -1
- package/types/mock-interceptor.d.ts +4 -4
- package/types/util.d.ts +4 -17
- package/types/webidl.d.ts +7 -8
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { webidl } = require('../fetch/webidl')
|
|
4
4
|
const { kEnumerableProperty } = require('../../core/util')
|
|
5
|
+
const { kConstruct } = require('../../core/symbols')
|
|
5
6
|
const { MessagePort } = require('node:worker_threads')
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -11,10 +12,16 @@ class MessageEvent extends Event {
|
|
|
11
12
|
#eventInit
|
|
12
13
|
|
|
13
14
|
constructor (type, eventInitDict = {}) {
|
|
14
|
-
|
|
15
|
+
if (type === kConstruct) {
|
|
16
|
+
super(arguments[1], arguments[2])
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const prefix = 'MessageEvent constructor'
|
|
21
|
+
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
15
22
|
|
|
16
|
-
type = webidl.converters.DOMString(type)
|
|
17
|
-
eventInitDict = webidl.converters.MessageEventInit(eventInitDict)
|
|
23
|
+
type = webidl.converters.DOMString(type, prefix, 'type')
|
|
24
|
+
eventInitDict = webidl.converters.MessageEventInit(eventInitDict, prefix, 'eventInitDict')
|
|
18
25
|
|
|
19
26
|
super(type, eventInitDict)
|
|
20
27
|
|
|
@@ -67,14 +74,28 @@ class MessageEvent extends Event {
|
|
|
67
74
|
) {
|
|
68
75
|
webidl.brandCheck(this, MessageEvent)
|
|
69
76
|
|
|
70
|
-
webidl.argumentLengthCheck(arguments, 1,
|
|
77
|
+
webidl.argumentLengthCheck(arguments, 1, 'MessageEvent.initMessageEvent')
|
|
71
78
|
|
|
72
79
|
return new MessageEvent(type, {
|
|
73
80
|
bubbles, cancelable, data, origin, lastEventId, source, ports
|
|
74
81
|
})
|
|
75
82
|
}
|
|
83
|
+
|
|
84
|
+
static createFastMessageEvent (type, init) {
|
|
85
|
+
const messageEvent = new MessageEvent(kConstruct, type, init)
|
|
86
|
+
messageEvent.#eventInit = init
|
|
87
|
+
messageEvent.#eventInit.data ??= null
|
|
88
|
+
messageEvent.#eventInit.origin ??= ''
|
|
89
|
+
messageEvent.#eventInit.lastEventId ??= ''
|
|
90
|
+
messageEvent.#eventInit.source ??= null
|
|
91
|
+
messageEvent.#eventInit.ports ??= []
|
|
92
|
+
return messageEvent
|
|
93
|
+
}
|
|
76
94
|
}
|
|
77
95
|
|
|
96
|
+
const { createFastMessageEvent } = MessageEvent
|
|
97
|
+
delete MessageEvent.createFastMessageEvent
|
|
98
|
+
|
|
78
99
|
/**
|
|
79
100
|
* @see https://websockets.spec.whatwg.org/#the-closeevent-interface
|
|
80
101
|
*/
|
|
@@ -82,9 +103,10 @@ class CloseEvent extends Event {
|
|
|
82
103
|
#eventInit
|
|
83
104
|
|
|
84
105
|
constructor (type, eventInitDict = {}) {
|
|
85
|
-
|
|
106
|
+
const prefix = 'CloseEvent constructor'
|
|
107
|
+
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
86
108
|
|
|
87
|
-
type = webidl.converters.DOMString(type)
|
|
109
|
+
type = webidl.converters.DOMString(type, prefix, 'type')
|
|
88
110
|
eventInitDict = webidl.converters.CloseEventInit(eventInitDict)
|
|
89
111
|
|
|
90
112
|
super(type, eventInitDict)
|
|
@@ -116,11 +138,12 @@ class ErrorEvent extends Event {
|
|
|
116
138
|
#eventInit
|
|
117
139
|
|
|
118
140
|
constructor (type, eventInitDict) {
|
|
119
|
-
|
|
141
|
+
const prefix = 'ErrorEvent constructor'
|
|
142
|
+
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
120
143
|
|
|
121
144
|
super(type, eventInitDict)
|
|
122
145
|
|
|
123
|
-
type = webidl.converters.DOMString(type)
|
|
146
|
+
type = webidl.converters.DOMString(type, prefix, 'type')
|
|
124
147
|
eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {})
|
|
125
148
|
|
|
126
149
|
this.#eventInit = eventInitDict
|
|
@@ -202,17 +225,17 @@ const eventInit = [
|
|
|
202
225
|
{
|
|
203
226
|
key: 'bubbles',
|
|
204
227
|
converter: webidl.converters.boolean,
|
|
205
|
-
defaultValue: false
|
|
228
|
+
defaultValue: () => false
|
|
206
229
|
},
|
|
207
230
|
{
|
|
208
231
|
key: 'cancelable',
|
|
209
232
|
converter: webidl.converters.boolean,
|
|
210
|
-
defaultValue: false
|
|
233
|
+
defaultValue: () => false
|
|
211
234
|
},
|
|
212
235
|
{
|
|
213
236
|
key: 'composed',
|
|
214
237
|
converter: webidl.converters.boolean,
|
|
215
|
-
defaultValue: false
|
|
238
|
+
defaultValue: () => false
|
|
216
239
|
}
|
|
217
240
|
]
|
|
218
241
|
|
|
@@ -221,31 +244,29 @@ webidl.converters.MessageEventInit = webidl.dictionaryConverter([
|
|
|
221
244
|
{
|
|
222
245
|
key: 'data',
|
|
223
246
|
converter: webidl.converters.any,
|
|
224
|
-
defaultValue: null
|
|
247
|
+
defaultValue: () => null
|
|
225
248
|
},
|
|
226
249
|
{
|
|
227
250
|
key: 'origin',
|
|
228
251
|
converter: webidl.converters.USVString,
|
|
229
|
-
defaultValue: ''
|
|
252
|
+
defaultValue: () => ''
|
|
230
253
|
},
|
|
231
254
|
{
|
|
232
255
|
key: 'lastEventId',
|
|
233
256
|
converter: webidl.converters.DOMString,
|
|
234
|
-
defaultValue: ''
|
|
257
|
+
defaultValue: () => ''
|
|
235
258
|
},
|
|
236
259
|
{
|
|
237
260
|
key: 'source',
|
|
238
261
|
// Node doesn't implement WindowProxy or ServiceWorker, so the only
|
|
239
262
|
// valid value for source is a MessagePort.
|
|
240
263
|
converter: webidl.nullableConverter(webidl.converters.MessagePort),
|
|
241
|
-
defaultValue: null
|
|
264
|
+
defaultValue: () => null
|
|
242
265
|
},
|
|
243
266
|
{
|
|
244
267
|
key: 'ports',
|
|
245
268
|
converter: webidl.converters['sequence<MessagePort>'],
|
|
246
|
-
|
|
247
|
-
return []
|
|
248
|
-
}
|
|
269
|
+
defaultValue: () => new Array(0)
|
|
249
270
|
}
|
|
250
271
|
])
|
|
251
272
|
|
|
@@ -254,17 +275,17 @@ webidl.converters.CloseEventInit = webidl.dictionaryConverter([
|
|
|
254
275
|
{
|
|
255
276
|
key: 'wasClean',
|
|
256
277
|
converter: webidl.converters.boolean,
|
|
257
|
-
defaultValue: false
|
|
278
|
+
defaultValue: () => false
|
|
258
279
|
},
|
|
259
280
|
{
|
|
260
281
|
key: 'code',
|
|
261
282
|
converter: webidl.converters['unsigned short'],
|
|
262
|
-
defaultValue: 0
|
|
283
|
+
defaultValue: () => 0
|
|
263
284
|
},
|
|
264
285
|
{
|
|
265
286
|
key: 'reason',
|
|
266
287
|
converter: webidl.converters.USVString,
|
|
267
|
-
defaultValue: ''
|
|
288
|
+
defaultValue: () => ''
|
|
268
289
|
}
|
|
269
290
|
])
|
|
270
291
|
|
|
@@ -273,22 +294,22 @@ webidl.converters.ErrorEventInit = webidl.dictionaryConverter([
|
|
|
273
294
|
{
|
|
274
295
|
key: 'message',
|
|
275
296
|
converter: webidl.converters.DOMString,
|
|
276
|
-
defaultValue: ''
|
|
297
|
+
defaultValue: () => ''
|
|
277
298
|
},
|
|
278
299
|
{
|
|
279
300
|
key: 'filename',
|
|
280
301
|
converter: webidl.converters.USVString,
|
|
281
|
-
defaultValue: ''
|
|
302
|
+
defaultValue: () => ''
|
|
282
303
|
},
|
|
283
304
|
{
|
|
284
305
|
key: 'lineno',
|
|
285
306
|
converter: webidl.converters['unsigned long'],
|
|
286
|
-
defaultValue: 0
|
|
307
|
+
defaultValue: () => 0
|
|
287
308
|
},
|
|
288
309
|
{
|
|
289
310
|
key: 'colno',
|
|
290
311
|
converter: webidl.converters['unsigned long'],
|
|
291
|
-
defaultValue: 0
|
|
312
|
+
defaultValue: () => 0
|
|
292
313
|
},
|
|
293
314
|
{
|
|
294
315
|
key: 'error',
|
|
@@ -299,5 +320,6 @@ webidl.converters.ErrorEventInit = webidl.dictionaryConverter([
|
|
|
299
320
|
module.exports = {
|
|
300
321
|
MessageEvent,
|
|
301
322
|
CloseEvent,
|
|
302
|
-
ErrorEvent
|
|
323
|
+
ErrorEvent,
|
|
324
|
+
createFastMessageEvent
|
|
303
325
|
}
|
|
@@ -2,13 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
const { maxUnsigned16Bit } = require('./constants')
|
|
4
4
|
|
|
5
|
+
const BUFFER_SIZE = 16386
|
|
6
|
+
|
|
5
7
|
/** @type {import('crypto')} */
|
|
6
8
|
let crypto
|
|
9
|
+
let buffer = null
|
|
10
|
+
let bufIdx = BUFFER_SIZE
|
|
11
|
+
|
|
7
12
|
try {
|
|
8
13
|
crypto = require('node:crypto')
|
|
9
14
|
/* c8 ignore next 3 */
|
|
10
15
|
} catch {
|
|
16
|
+
crypto = {
|
|
17
|
+
// not full compatibility, but minimum.
|
|
18
|
+
randomFillSync: function randomFillSync (buffer, _offset, _size) {
|
|
19
|
+
for (let i = 0; i < buffer.length; ++i) {
|
|
20
|
+
buffer[i] = Math.random() * 255 | 0
|
|
21
|
+
}
|
|
22
|
+
return buffer
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
11
26
|
|
|
27
|
+
function generateMask () {
|
|
28
|
+
if (bufIdx === BUFFER_SIZE) {
|
|
29
|
+
bufIdx = 0
|
|
30
|
+
crypto.randomFillSync((buffer ??= Buffer.allocUnsafe(BUFFER_SIZE)), 0, BUFFER_SIZE)
|
|
31
|
+
}
|
|
32
|
+
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
|
|
12
33
|
}
|
|
13
34
|
|
|
14
35
|
class WebsocketFrameSend {
|
|
@@ -17,11 +38,12 @@ class WebsocketFrameSend {
|
|
|
17
38
|
*/
|
|
18
39
|
constructor (data) {
|
|
19
40
|
this.frameData = data
|
|
20
|
-
this.maskKey = crypto.randomBytes(4)
|
|
21
41
|
}
|
|
22
42
|
|
|
23
43
|
createFrame (opcode) {
|
|
24
|
-
const
|
|
44
|
+
const frameData = this.frameData
|
|
45
|
+
const maskKey = generateMask()
|
|
46
|
+
const bodyLength = frameData?.byteLength ?? 0
|
|
25
47
|
|
|
26
48
|
/** @type {number} */
|
|
27
49
|
let payloadLength = bodyLength // 0-125
|
|
@@ -43,10 +65,10 @@ class WebsocketFrameSend {
|
|
|
43
65
|
buffer[0] = (buffer[0] & 0xF0) + opcode // opcode
|
|
44
66
|
|
|
45
67
|
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
|
|
46
|
-
buffer[offset - 4] =
|
|
47
|
-
buffer[offset - 3] =
|
|
48
|
-
buffer[offset - 2] =
|
|
49
|
-
buffer[offset - 1] =
|
|
68
|
+
buffer[offset - 4] = maskKey[0]
|
|
69
|
+
buffer[offset - 3] = maskKey[1]
|
|
70
|
+
buffer[offset - 2] = maskKey[2]
|
|
71
|
+
buffer[offset - 1] = maskKey[3]
|
|
50
72
|
|
|
51
73
|
buffer[1] = payloadLength
|
|
52
74
|
|
|
@@ -61,8 +83,8 @@ class WebsocketFrameSend {
|
|
|
61
83
|
buffer[1] |= 0x80 // MASK
|
|
62
84
|
|
|
63
85
|
// mask body
|
|
64
|
-
for (let i = 0; i < bodyLength; i
|
|
65
|
-
buffer[offset + i] =
|
|
86
|
+
for (let i = 0; i < bodyLength; ++i) {
|
|
87
|
+
buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
return buffer
|
|
@@ -6,6 +6,7 @@ const { kReadyState, kSentClose, kResponse, kReceivedClose } = require('./symbol
|
|
|
6
6
|
const { channels } = require('../../core/diagnostics')
|
|
7
7
|
const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived, utf8Decode } = require('./util')
|
|
8
8
|
const { WebsocketFrameSend } = require('./frame')
|
|
9
|
+
const { CloseEvent } = require('./events')
|
|
9
10
|
|
|
10
11
|
// This code was influenced by ws released under the MIT license.
|
|
11
12
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
@@ -55,6 +56,12 @@ class ByteParser extends Writable {
|
|
|
55
56
|
|
|
56
57
|
this.#info.fin = (buffer[0] & 0x80) !== 0
|
|
57
58
|
this.#info.opcode = buffer[0] & 0x0F
|
|
59
|
+
this.#info.masked = (buffer[1] & 0x80) === 0x80
|
|
60
|
+
|
|
61
|
+
if (this.#info.masked) {
|
|
62
|
+
failWebsocketConnection(this.ws, 'Frame cannot be masked')
|
|
63
|
+
return callback()
|
|
64
|
+
}
|
|
58
65
|
|
|
59
66
|
// If we receive a fragmented message, we use the type of the first
|
|
60
67
|
// frame to parse the full message as binary/text, when it's terminated
|
|
@@ -102,6 +109,13 @@ class ByteParser extends Writable {
|
|
|
102
109
|
|
|
103
110
|
this.#info.closeInfo = this.parseCloseBody(body)
|
|
104
111
|
|
|
112
|
+
if (this.#info.closeInfo.error) {
|
|
113
|
+
const { code, reason } = this.#info.closeInfo
|
|
114
|
+
|
|
115
|
+
callback(new CloseEvent('close', { wasClean: false, reason, code }))
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
if (this.ws[kSentClose] !== sentCloseFrameState.SENT) {
|
|
106
120
|
// If an endpoint receives a Close frame and did not previously send a
|
|
107
121
|
// Close frame, the endpoint MUST send a Close frame in response. (When
|
|
@@ -239,7 +253,7 @@ class ByteParser extends Writable {
|
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
|
|
242
|
-
if (this.#byteOffset === 0) {
|
|
256
|
+
if (this.#byteOffset === 0 && this.#info.payloadLength !== 0) {
|
|
243
257
|
callback()
|
|
244
258
|
break
|
|
245
259
|
}
|
|
@@ -310,16 +324,16 @@ class ByteParser extends Writable {
|
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
if (code !== undefined && !isValidStatusCode(code)) {
|
|
313
|
-
return
|
|
327
|
+
return { code: 1002, reason: 'Invalid status code', error: true }
|
|
314
328
|
}
|
|
315
329
|
|
|
316
330
|
try {
|
|
317
331
|
reason = utf8Decode(reason)
|
|
318
332
|
} catch {
|
|
319
|
-
return
|
|
333
|
+
return { code: 1007, reason: 'Invalid UTF-8', error: true }
|
|
320
334
|
}
|
|
321
335
|
|
|
322
|
-
return { code, reason }
|
|
336
|
+
return { code, reason, error: false }
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
get closingInfo () {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require('./symbols')
|
|
4
4
|
const { states, opcodes } = require('./constants')
|
|
5
|
-
const {
|
|
5
|
+
const { ErrorEvent, createFastMessageEvent } = require('./events')
|
|
6
6
|
const { isUtf8 } = require('node:buffer')
|
|
7
7
|
|
|
8
8
|
/* globals Blob */
|
|
@@ -51,15 +51,16 @@ function isClosed (ws) {
|
|
|
51
51
|
* @see https://dom.spec.whatwg.org/#concept-event-fire
|
|
52
52
|
* @param {string} e
|
|
53
53
|
* @param {EventTarget} target
|
|
54
|
+
* @param {(...args: ConstructorParameters<typeof Event>) => Event} eventFactory
|
|
54
55
|
* @param {EventInit | undefined} eventInitDict
|
|
55
56
|
*/
|
|
56
|
-
function fireEvent (e, target,
|
|
57
|
+
function fireEvent (e, target, eventFactory = (type, init) => new Event(type, init), eventInitDict = {}) {
|
|
57
58
|
// 1. If eventConstructor is not given, then let eventConstructor be Event.
|
|
58
59
|
|
|
59
60
|
// 2. Let event be the result of creating an event given eventConstructor,
|
|
60
61
|
// in the relevant realm of target.
|
|
61
62
|
// 3. Initialize event’s type attribute to e.
|
|
62
|
-
const event =
|
|
63
|
+
const event = eventFactory(e, eventInitDict)
|
|
63
64
|
|
|
64
65
|
// 4. Initialize any other IDL attributes of event as described in the
|
|
65
66
|
// invocation of this algorithm.
|
|
@@ -103,19 +104,26 @@ function websocketMessageReceived (ws, type, data) {
|
|
|
103
104
|
// -> type indicates that the data is Binary and binary type is "arraybuffer"
|
|
104
105
|
// a new ArrayBuffer object, created in the relevant Realm of the
|
|
105
106
|
// WebSocket object, whose contents are data
|
|
106
|
-
dataForEvent =
|
|
107
|
+
dataForEvent = toArrayBuffer(data)
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
// 3. Fire an event named message at the WebSocket object, using MessageEvent,
|
|
111
112
|
// with the origin attribute initialized to the serialization of the WebSocket
|
|
112
113
|
// object’s url's origin, and the data attribute initialized to dataForEvent.
|
|
113
|
-
fireEvent('message', ws,
|
|
114
|
+
fireEvent('message', ws, createFastMessageEvent, {
|
|
114
115
|
origin: ws[kWebSocketURL].origin,
|
|
115
116
|
data: dataForEvent
|
|
116
117
|
})
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
function toArrayBuffer (buffer) {
|
|
121
|
+
if (buffer.byteLength === buffer.buffer.byteLength) {
|
|
122
|
+
return buffer.buffer
|
|
123
|
+
}
|
|
124
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
/**
|
|
120
128
|
* @see https://datatracker.ietf.org/doc/html/rfc6455
|
|
121
129
|
* @see https://datatracker.ietf.org/doc/html/rfc2616
|
|
@@ -195,8 +203,9 @@ function failWebsocketConnection (ws, reason) {
|
|
|
195
203
|
|
|
196
204
|
if (reason) {
|
|
197
205
|
// TODO: process.nextTick
|
|
198
|
-
fireEvent('error', ws, ErrorEvent, {
|
|
199
|
-
error: new Error(reason)
|
|
206
|
+
fireEvent('error', ws, (type, init) => new ErrorEvent(type, init), {
|
|
207
|
+
error: new Error(reason),
|
|
208
|
+
message: reason
|
|
200
209
|
})
|
|
201
210
|
}
|
|
202
211
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { webidl } = require('../fetch/webidl')
|
|
4
4
|
const { URLSerializer } = require('../fetch/data-url')
|
|
5
|
-
const {
|
|
6
|
-
const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes
|
|
5
|
+
const { environmentSettingsObject } = require('../fetch/util')
|
|
6
|
+
const { staticPropertyDescriptors, states, sentCloseFrameState, opcodes } = require('./constants')
|
|
7
7
|
const {
|
|
8
8
|
kWebSocketURL,
|
|
9
9
|
kReadyState,
|
|
@@ -16,21 +16,22 @@ const {
|
|
|
16
16
|
const {
|
|
17
17
|
isConnecting,
|
|
18
18
|
isEstablished,
|
|
19
|
-
isClosed,
|
|
20
19
|
isClosing,
|
|
21
20
|
isValidSubprotocol,
|
|
22
|
-
failWebsocketConnection,
|
|
23
21
|
fireEvent
|
|
24
22
|
} = require('./util')
|
|
25
|
-
const { establishWebSocketConnection } = require('./connection')
|
|
23
|
+
const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
|
|
26
24
|
const { WebsocketFrameSend } = require('./frame')
|
|
27
25
|
const { ByteParser } = require('./receiver')
|
|
28
26
|
const { kEnumerableProperty, isBlobLike } = require('../../core/util')
|
|
29
27
|
const { getGlobalDispatcher } = require('../../global')
|
|
30
28
|
const { types } = require('node:util')
|
|
29
|
+
const { ErrorEvent } = require('./events')
|
|
31
30
|
|
|
32
31
|
let experimentalWarned = false
|
|
33
32
|
|
|
33
|
+
const FastBuffer = Buffer[Symbol.species]
|
|
34
|
+
|
|
34
35
|
// https://websockets.spec.whatwg.org/#interface-definition
|
|
35
36
|
class WebSocket extends EventTarget {
|
|
36
37
|
#events = {
|
|
@@ -51,7 +52,8 @@ class WebSocket extends EventTarget {
|
|
|
51
52
|
constructor (url, protocols = []) {
|
|
52
53
|
super()
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
const prefix = 'WebSocket constructor'
|
|
56
|
+
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
55
57
|
|
|
56
58
|
if (!experimentalWarned) {
|
|
57
59
|
experimentalWarned = true
|
|
@@ -60,13 +62,13 @@ class WebSocket extends EventTarget {
|
|
|
60
62
|
})
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols)
|
|
65
|
+
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols, prefix, 'options')
|
|
64
66
|
|
|
65
|
-
url = webidl.converters.USVString(url)
|
|
67
|
+
url = webidl.converters.USVString(url, prefix, 'url')
|
|
66
68
|
protocols = options.protocols
|
|
67
69
|
|
|
68
70
|
// 1. Let baseURL be this's relevant settings object's API base URL.
|
|
69
|
-
const baseURL =
|
|
71
|
+
const baseURL = environmentSettingsObject.settingsObject.baseUrl
|
|
70
72
|
|
|
71
73
|
// 1. Let urlRecord be the result of applying the URL parser to url with baseURL.
|
|
72
74
|
let urlRecord
|
|
@@ -159,12 +161,14 @@ class WebSocket extends EventTarget {
|
|
|
159
161
|
close (code = undefined, reason = undefined) {
|
|
160
162
|
webidl.brandCheck(this, WebSocket)
|
|
161
163
|
|
|
164
|
+
const prefix = 'WebSocket.close'
|
|
165
|
+
|
|
162
166
|
if (code !== undefined) {
|
|
163
|
-
code = webidl.converters['unsigned short'](code, { clamp: true })
|
|
167
|
+
code = webidl.converters['unsigned short'](code, prefix, 'code', { clamp: true })
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
if (reason !== undefined) {
|
|
167
|
-
reason = webidl.converters.USVString(reason)
|
|
171
|
+
reason = webidl.converters.USVString(reason, prefix, 'reason')
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
// 1. If code is present, but is neither an integer equal to 1000 nor an
|
|
@@ -194,67 +198,7 @@ class WebSocket extends EventTarget {
|
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
// 3. Run the first matching steps from the following list:
|
|
197
|
-
|
|
198
|
-
// If this's ready state is CLOSING (2) or CLOSED (3)
|
|
199
|
-
// Do nothing.
|
|
200
|
-
} else if (!isEstablished(this)) {
|
|
201
|
-
// If the WebSocket connection is not yet established
|
|
202
|
-
// Fail the WebSocket connection and set this's ready state
|
|
203
|
-
// to CLOSING (2).
|
|
204
|
-
failWebsocketConnection(this, 'Connection was closed before it was established.')
|
|
205
|
-
this[kReadyState] = WebSocket.CLOSING
|
|
206
|
-
} else if (this[kSentClose] === sentCloseFrameState.NOT_SENT) {
|
|
207
|
-
// If the WebSocket closing handshake has not yet been started
|
|
208
|
-
// Start the WebSocket closing handshake and set this's ready
|
|
209
|
-
// state to CLOSING (2).
|
|
210
|
-
// - If neither code nor reason is present, the WebSocket Close
|
|
211
|
-
// message must not have a body.
|
|
212
|
-
// - If code is present, then the status code to use in the
|
|
213
|
-
// WebSocket Close message must be the integer given by code.
|
|
214
|
-
// - If reason is also present, then reasonBytes must be
|
|
215
|
-
// provided in the Close message after the status code.
|
|
216
|
-
|
|
217
|
-
this[kSentClose] = sentCloseFrameState.PROCESSING
|
|
218
|
-
|
|
219
|
-
const frame = new WebsocketFrameSend()
|
|
220
|
-
|
|
221
|
-
// If neither code nor reason is present, the WebSocket Close
|
|
222
|
-
// message must not have a body.
|
|
223
|
-
|
|
224
|
-
// If code is present, then the status code to use in the
|
|
225
|
-
// WebSocket Close message must be the integer given by code.
|
|
226
|
-
if (code !== undefined && reason === undefined) {
|
|
227
|
-
frame.frameData = Buffer.allocUnsafe(2)
|
|
228
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
229
|
-
} else if (code !== undefined && reason !== undefined) {
|
|
230
|
-
// If reason is also present, then reasonBytes must be
|
|
231
|
-
// provided in the Close message after the status code.
|
|
232
|
-
frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
|
|
233
|
-
frame.frameData.writeUInt16BE(code, 0)
|
|
234
|
-
// the body MAY contain UTF-8-encoded data with value /reason/
|
|
235
|
-
frame.frameData.write(reason, 2, 'utf-8')
|
|
236
|
-
} else {
|
|
237
|
-
frame.frameData = emptyBuffer
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** @type {import('stream').Duplex} */
|
|
241
|
-
const socket = this[kResponse].socket
|
|
242
|
-
|
|
243
|
-
socket.write(frame.createFrame(opcodes.CLOSE), (err) => {
|
|
244
|
-
if (!err) {
|
|
245
|
-
this[kSentClose] = sentCloseFrameState.SENT
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
// Upon either sending or receiving a Close control frame, it is said
|
|
250
|
-
// that _The WebSocket Closing Handshake is Started_ and that the
|
|
251
|
-
// WebSocket connection is in the CLOSING state.
|
|
252
|
-
this[kReadyState] = states.CLOSING
|
|
253
|
-
} else {
|
|
254
|
-
// Otherwise
|
|
255
|
-
// Set this's ready state to CLOSING (2).
|
|
256
|
-
this[kReadyState] = WebSocket.CLOSING
|
|
257
|
-
}
|
|
201
|
+
closeWebSocketConnection(this, code, reason, reasonByteLength)
|
|
258
202
|
}
|
|
259
203
|
|
|
260
204
|
/**
|
|
@@ -264,9 +208,10 @@ class WebSocket extends EventTarget {
|
|
|
264
208
|
send (data) {
|
|
265
209
|
webidl.brandCheck(this, WebSocket)
|
|
266
210
|
|
|
267
|
-
|
|
211
|
+
const prefix = 'WebSocket.send'
|
|
212
|
+
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
268
213
|
|
|
269
|
-
data = webidl.converters.WebSocketSendData(data)
|
|
214
|
+
data = webidl.converters.WebSocketSendData(data, prefix, 'data')
|
|
270
215
|
|
|
271
216
|
// 1. If this's ready state is CONNECTING, then throw an
|
|
272
217
|
// "InvalidStateError" DOMException.
|
|
@@ -319,7 +264,7 @@ class WebSocket extends EventTarget {
|
|
|
319
264
|
// increase the bufferedAmount attribute by the length of the
|
|
320
265
|
// ArrayBuffer in bytes.
|
|
321
266
|
|
|
322
|
-
const value =
|
|
267
|
+
const value = new FastBuffer(data)
|
|
323
268
|
const frame = new WebsocketFrameSend(value)
|
|
324
269
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|
325
270
|
|
|
@@ -340,7 +285,7 @@ class WebSocket extends EventTarget {
|
|
|
340
285
|
// not throw an exception must increase the bufferedAmount attribute
|
|
341
286
|
// by the length of data’s buffer in bytes.
|
|
342
287
|
|
|
343
|
-
const ab =
|
|
288
|
+
const ab = new FastBuffer(data, data.byteOffset, data.byteLength)
|
|
344
289
|
|
|
345
290
|
const frame = new WebsocketFrameSend(ab)
|
|
346
291
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|
@@ -364,7 +309,7 @@ class WebSocket extends EventTarget {
|
|
|
364
309
|
const frame = new WebsocketFrameSend()
|
|
365
310
|
|
|
366
311
|
data.arrayBuffer().then((ab) => {
|
|
367
|
-
const value =
|
|
312
|
+
const value = new FastBuffer(ab)
|
|
368
313
|
frame.frameData = value
|
|
369
314
|
const buffer = frame.createFrame(opcodes.BINARY)
|
|
370
315
|
|
|
@@ -517,9 +462,8 @@ class WebSocket extends EventTarget {
|
|
|
517
462
|
this[kResponse] = response
|
|
518
463
|
|
|
519
464
|
const parser = new ByteParser(this)
|
|
520
|
-
parser.on('drain',
|
|
521
|
-
|
|
522
|
-
})
|
|
465
|
+
parser.on('drain', onParserDrain)
|
|
466
|
+
parser.on('error', onParserError.bind(this))
|
|
523
467
|
|
|
524
468
|
response.socket.ws = this
|
|
525
469
|
this[kByteParser] = parser
|
|
@@ -595,12 +539,12 @@ webidl.converters['sequence<DOMString>'] = webidl.sequenceConverter(
|
|
|
595
539
|
webidl.converters.DOMString
|
|
596
540
|
)
|
|
597
541
|
|
|
598
|
-
webidl.converters['DOMString or sequence<DOMString>'] = function (V) {
|
|
542
|
+
webidl.converters['DOMString or sequence<DOMString>'] = function (V, prefix, argument) {
|
|
599
543
|
if (webidl.util.Type(V) === 'Object' && Symbol.iterator in V) {
|
|
600
544
|
return webidl.converters['sequence<DOMString>'](V)
|
|
601
545
|
}
|
|
602
546
|
|
|
603
|
-
return webidl.converters.DOMString(V)
|
|
547
|
+
return webidl.converters.DOMString(V, prefix, argument)
|
|
604
548
|
}
|
|
605
549
|
|
|
606
550
|
// This implements the propsal made in https://github.com/whatwg/websockets/issues/42
|
|
@@ -608,16 +552,12 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
|
|
|
608
552
|
{
|
|
609
553
|
key: 'protocols',
|
|
610
554
|
converter: webidl.converters['DOMString or sequence<DOMString>'],
|
|
611
|
-
|
|
612
|
-
return []
|
|
613
|
-
}
|
|
555
|
+
defaultValue: () => new Array(0)
|
|
614
556
|
},
|
|
615
557
|
{
|
|
616
558
|
key: 'dispatcher',
|
|
617
559
|
converter: (V) => V,
|
|
618
|
-
|
|
619
|
-
return getGlobalDispatcher()
|
|
620
|
-
}
|
|
560
|
+
defaultValue: () => getGlobalDispatcher()
|
|
621
561
|
},
|
|
622
562
|
{
|
|
623
563
|
key: 'headers',
|
|
@@ -647,6 +587,16 @@ webidl.converters.WebSocketSendData = function (V) {
|
|
|
647
587
|
return webidl.converters.USVString(V)
|
|
648
588
|
}
|
|
649
589
|
|
|
590
|
+
function onParserDrain () {
|
|
591
|
+
this.ws[kResponse].socket.resume()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function onParserError (err) {
|
|
595
|
+
fireEvent('error', this, () => new ErrorEvent('error', { error: err, message: err.reason }))
|
|
596
|
+
|
|
597
|
+
closeWebSocketConnection(this, err.code)
|
|
598
|
+
}
|
|
599
|
+
|
|
650
600
|
module.exports = {
|
|
651
601
|
WebSocket
|
|
652
602
|
}
|