eveee 1.0.6

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.
@@ -0,0 +1,607 @@
1
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */
2
+
3
+ 'use strict';
4
+
5
+ const { Duplex } = require('stream');
6
+ const { randomFillSync } = require('crypto');
7
+ const {
8
+ types: { isUint8Array }
9
+ } = require('util');
10
+
11
+ const PerMessageDeflate = require('./permessage-deflate');
12
+ const { EMPTY_BUFFER, kWebSocket, NOOP } = require('./constants');
13
+ const { isBlob, isValidStatusCode } = require('./validation');
14
+ const { mask: applyMask, toBuffer } = require('./buffer-util');
15
+
16
+ const kByteLength = Symbol('kByteLength');
17
+ const maskBuffer = Buffer.alloc(4);
18
+ const RANDOM_POOL_SIZE = 8 * 1024;
19
+ let randomPool;
20
+ let randomPoolPointer = RANDOM_POOL_SIZE;
21
+
22
+ const DEFAULT = 0;
23
+ const DEFLATING = 1;
24
+ const GET_BLOB_DATA = 2;
25
+
26
+ /**
27
+ * HyBi Sender implementation.
28
+ */
29
+ class Sender {
30
+ /**
31
+ * Creates a Sender instance.
32
+ *
33
+ * @param {Duplex} socket The connection socket
34
+ * @param {Object} [extensions] An object containing the negotiated extensions
35
+ * @param {Function} [generateMask] The function used to generate the masking
36
+ * key
37
+ */
38
+ constructor(socket, extensions, generateMask) {
39
+ this._extensions = extensions || {};
40
+
41
+ if (generateMask) {
42
+ this._generateMask = generateMask;
43
+ this._maskBuffer = Buffer.alloc(4);
44
+ }
45
+
46
+ this._socket = socket;
47
+
48
+ this._firstFragment = true;
49
+ this._compress = false;
50
+
51
+ this._bufferedBytes = 0;
52
+ this._queue = [];
53
+ this._state = DEFAULT;
54
+ this.onerror = NOOP;
55
+ this[kWebSocket] = undefined;
56
+ }
57
+
58
+ /**
59
+ * Frames a piece of data according to the HyBi WebSocket protocol.
60
+ *
61
+ * @param {(Buffer|String)} data The data to frame
62
+ * @param {Object} options Options object
63
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
64
+ * FIN bit
65
+ * @param {Function} [options.generateMask] The function used to generate the
66
+ * masking key
67
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
68
+ * `data`
69
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
70
+ * key
71
+ * @param {Number} options.opcode The opcode
72
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
73
+ * modified
74
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
75
+ * RSV1 bit
76
+ * @return {(Buffer|String)[]} The framed data
77
+ * @public
78
+ */
79
+ static frame(data, options) {
80
+ let mask;
81
+ let merge = false;
82
+ let offset = 2;
83
+ let skipMasking = false;
84
+
85
+ if (options.mask) {
86
+ mask = options.maskBuffer || maskBuffer;
87
+
88
+ if (options.generateMask) {
89
+ options.generateMask(mask);
90
+ } else {
91
+ if (randomPoolPointer === RANDOM_POOL_SIZE) {
92
+ /* istanbul ignore else */
93
+ if (randomPool === undefined) {
94
+ //
95
+ // This is lazily initialized because server-sent frames must not
96
+ // be masked so it may never be used.
97
+ //
98
+ randomPool = Buffer.alloc(RANDOM_POOL_SIZE);
99
+ }
100
+
101
+ randomFillSync(randomPool, 0, RANDOM_POOL_SIZE);
102
+ randomPoolPointer = 0;
103
+ }
104
+
105
+ mask[0] = randomPool[randomPoolPointer++];
106
+ mask[1] = randomPool[randomPoolPointer++];
107
+ mask[2] = randomPool[randomPoolPointer++];
108
+ mask[3] = randomPool[randomPoolPointer++];
109
+ }
110
+
111
+ skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
112
+ offset = 6;
113
+ }
114
+
115
+ let dataLength;
116
+
117
+ if (typeof data === 'string') {
118
+ if (
119
+ (!options.mask || skipMasking) &&
120
+ options[kByteLength] !== undefined
121
+ ) {
122
+ dataLength = options[kByteLength];
123
+ } else {
124
+ data = Buffer.from(data);
125
+ dataLength = data.length;
126
+ }
127
+ } else {
128
+ dataLength = data.length;
129
+ merge = options.mask && options.readOnly && !skipMasking;
130
+ }
131
+
132
+ let payloadLength = dataLength;
133
+
134
+ if (dataLength >= 65536) {
135
+ offset += 8;
136
+ payloadLength = 127;
137
+ } else if (dataLength > 125) {
138
+ offset += 2;
139
+ payloadLength = 126;
140
+ }
141
+
142
+ const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
143
+
144
+ target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
145
+ if (options.rsv1) target[0] |= 0x40;
146
+
147
+ target[1] = payloadLength;
148
+
149
+ if (payloadLength === 126) {
150
+ target.writeUInt16BE(dataLength, 2);
151
+ } else if (payloadLength === 127) {
152
+ target[2] = target[3] = 0;
153
+ target.writeUIntBE(dataLength, 4, 6);
154
+ }
155
+
156
+ if (!options.mask) return [target, data];
157
+
158
+ target[1] |= 0x80;
159
+ target[offset - 4] = mask[0];
160
+ target[offset - 3] = mask[1];
161
+ target[offset - 2] = mask[2];
162
+ target[offset - 1] = mask[3];
163
+
164
+ if (skipMasking) return [target, data];
165
+
166
+ if (merge) {
167
+ applyMask(data, mask, target, offset, dataLength);
168
+ return [target];
169
+ }
170
+
171
+ applyMask(data, mask, data, 0, dataLength);
172
+ return [target, data];
173
+ }
174
+
175
+ /**
176
+ * Sends a close message to the other peer.
177
+ *
178
+ * @param {Number} [code] The status code component of the body
179
+ * @param {(String|Buffer)} [data] The message component of the body
180
+ * @param {Boolean} [mask=false] Specifies whether or not to mask the message
181
+ * @param {Function} [cb] Callback
182
+ * @public
183
+ */
184
+ close(code, data, mask, cb) {
185
+ let buf;
186
+
187
+ if (code === undefined) {
188
+ buf = EMPTY_BUFFER;
189
+ } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
190
+ throw new TypeError('First argument must be a valid error code number');
191
+ } else if (data === undefined || !data.length) {
192
+ buf = Buffer.allocUnsafe(2);
193
+ buf.writeUInt16BE(code, 0);
194
+ } else {
195
+ const length = Buffer.byteLength(data);
196
+
197
+ if (length > 123) {
198
+ throw new RangeError('The message must not be greater than 123 bytes');
199
+ }
200
+
201
+ buf = Buffer.allocUnsafe(2 + length);
202
+ buf.writeUInt16BE(code, 0);
203
+
204
+ if (typeof data === 'string') {
205
+ buf.write(data, 2);
206
+ } else if (isUint8Array(data)) {
207
+ buf.set(data, 2);
208
+ } else {
209
+ throw new TypeError('Second argument must be a string or a Uint8Array');
210
+ }
211
+ }
212
+
213
+ const options = {
214
+ [kByteLength]: buf.length,
215
+ fin: true,
216
+ generateMask: this._generateMask,
217
+ mask,
218
+ maskBuffer: this._maskBuffer,
219
+ opcode: 0x08,
220
+ readOnly: false,
221
+ rsv1: false
222
+ };
223
+
224
+ if (this._state !== DEFAULT) {
225
+ this.enqueue([this.dispatch, buf, false, options, cb]);
226
+ } else {
227
+ this.sendFrame(Sender.frame(buf, options), cb);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Sends a ping message to the other peer.
233
+ *
234
+ * @param {*} data The message to send
235
+ * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
236
+ * @param {Function} [cb] Callback
237
+ * @public
238
+ */
239
+ ping(data, mask, cb) {
240
+ let byteLength;
241
+ let readOnly;
242
+
243
+ if (typeof data === 'string') {
244
+ byteLength = Buffer.byteLength(data);
245
+ readOnly = false;
246
+ } else if (isBlob(data)) {
247
+ byteLength = data.size;
248
+ readOnly = false;
249
+ } else {
250
+ data = toBuffer(data);
251
+ byteLength = data.length;
252
+ readOnly = toBuffer.readOnly;
253
+ }
254
+
255
+ if (byteLength > 125) {
256
+ throw new RangeError('The data size must not be greater than 125 bytes');
257
+ }
258
+
259
+ const options = {
260
+ [kByteLength]: byteLength,
261
+ fin: true,
262
+ generateMask: this._generateMask,
263
+ mask,
264
+ maskBuffer: this._maskBuffer,
265
+ opcode: 0x09,
266
+ readOnly,
267
+ rsv1: false
268
+ };
269
+
270
+ if (isBlob(data)) {
271
+ if (this._state !== DEFAULT) {
272
+ this.enqueue([this.getBlobData, data, false, options, cb]);
273
+ } else {
274
+ this.getBlobData(data, false, options, cb);
275
+ }
276
+ } else if (this._state !== DEFAULT) {
277
+ this.enqueue([this.dispatch, data, false, options, cb]);
278
+ } else {
279
+ this.sendFrame(Sender.frame(data, options), cb);
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Sends a pong message to the other peer.
285
+ *
286
+ * @param {*} data The message to send
287
+ * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
288
+ * @param {Function} [cb] Callback
289
+ * @public
290
+ */
291
+ pong(data, mask, cb) {
292
+ let byteLength;
293
+ let readOnly;
294
+
295
+ if (typeof data === 'string') {
296
+ byteLength = Buffer.byteLength(data);
297
+ readOnly = false;
298
+ } else if (isBlob(data)) {
299
+ byteLength = data.size;
300
+ readOnly = false;
301
+ } else {
302
+ data = toBuffer(data);
303
+ byteLength = data.length;
304
+ readOnly = toBuffer.readOnly;
305
+ }
306
+
307
+ if (byteLength > 125) {
308
+ throw new RangeError('The data size must not be greater than 125 bytes');
309
+ }
310
+
311
+ const options = {
312
+ [kByteLength]: byteLength,
313
+ fin: true,
314
+ generateMask: this._generateMask,
315
+ mask,
316
+ maskBuffer: this._maskBuffer,
317
+ opcode: 0x0a,
318
+ readOnly,
319
+ rsv1: false
320
+ };
321
+
322
+ if (isBlob(data)) {
323
+ if (this._state !== DEFAULT) {
324
+ this.enqueue([this.getBlobData, data, false, options, cb]);
325
+ } else {
326
+ this.getBlobData(data, false, options, cb);
327
+ }
328
+ } else if (this._state !== DEFAULT) {
329
+ this.enqueue([this.dispatch, data, false, options, cb]);
330
+ } else {
331
+ this.sendFrame(Sender.frame(data, options), cb);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Sends a data message to the other peer.
337
+ *
338
+ * @param {*} data The message to send
339
+ * @param {Object} options Options object
340
+ * @param {Boolean} [options.binary=false] Specifies whether `data` is binary
341
+ * or text
342
+ * @param {Boolean} [options.compress=false] Specifies whether or not to
343
+ * compress `data`
344
+ * @param {Boolean} [options.fin=false] Specifies whether the fragment is the
345
+ * last one
346
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
347
+ * `data`
348
+ * @param {Function} [cb] Callback
349
+ * @public
350
+ */
351
+ send(data, options, cb) {
352
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
353
+ let opcode = options.binary ? 2 : 1;
354
+ let rsv1 = options.compress;
355
+
356
+ let byteLength;
357
+ let readOnly;
358
+
359
+ if (typeof data === 'string') {
360
+ byteLength = Buffer.byteLength(data);
361
+ readOnly = false;
362
+ } else if (isBlob(data)) {
363
+ byteLength = data.size;
364
+ readOnly = false;
365
+ } else {
366
+ data = toBuffer(data);
367
+ byteLength = data.length;
368
+ readOnly = toBuffer.readOnly;
369
+ }
370
+
371
+ if (this._firstFragment) {
372
+ this._firstFragment = false;
373
+ if (
374
+ rsv1 &&
375
+ perMessageDeflate &&
376
+ perMessageDeflate.params[
377
+ perMessageDeflate._isServer
378
+ ? 'server_no_context_takeover'
379
+ : 'client_no_context_takeover'
380
+ ]
381
+ ) {
382
+ rsv1 = byteLength >= perMessageDeflate._threshold;
383
+ }
384
+ this._compress = rsv1;
385
+ } else {
386
+ rsv1 = false;
387
+ opcode = 0;
388
+ }
389
+
390
+ if (options.fin) this._firstFragment = true;
391
+
392
+ const opts = {
393
+ [kByteLength]: byteLength,
394
+ fin: options.fin,
395
+ generateMask: this._generateMask,
396
+ mask: options.mask,
397
+ maskBuffer: this._maskBuffer,
398
+ opcode,
399
+ readOnly,
400
+ rsv1
401
+ };
402
+
403
+ if (isBlob(data)) {
404
+ if (this._state !== DEFAULT) {
405
+ this.enqueue([this.getBlobData, data, this._compress, opts, cb]);
406
+ } else {
407
+ this.getBlobData(data, this._compress, opts, cb);
408
+ }
409
+ } else if (this._state !== DEFAULT) {
410
+ this.enqueue([this.dispatch, data, this._compress, opts, cb]);
411
+ } else {
412
+ this.dispatch(data, this._compress, opts, cb);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Gets the contents of a blob as binary data.
418
+ *
419
+ * @param {Blob} blob The blob
420
+ * @param {Boolean} [compress=false] Specifies whether or not to compress
421
+ * the data
422
+ * @param {Object} options Options object
423
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
424
+ * FIN bit
425
+ * @param {Function} [options.generateMask] The function used to generate the
426
+ * masking key
427
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
428
+ * `data`
429
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
430
+ * key
431
+ * @param {Number} options.opcode The opcode
432
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
433
+ * modified
434
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
435
+ * RSV1 bit
436
+ * @param {Function} [cb] Callback
437
+ * @private
438
+ */
439
+ getBlobData(blob, compress, options, cb) {
440
+ this._bufferedBytes += options[kByteLength];
441
+ this._state = GET_BLOB_DATA;
442
+
443
+ blob
444
+ .arrayBuffer()
445
+ .then((arrayBuffer) => {
446
+ if (this._socket.destroyed) {
447
+ const err = new Error(
448
+ 'The socket was closed while the blob was being read'
449
+ );
450
+
451
+ //
452
+ // `callCallbacks` is called in the next tick to ensure that errors
453
+ // that might be thrown in the callbacks behave like errors thrown
454
+ // outside the promise chain.
455
+ //
456
+ process.nextTick(callCallbacks, this, err, cb);
457
+ return;
458
+ }
459
+
460
+ this._bufferedBytes -= options[kByteLength];
461
+ const data = toBuffer(arrayBuffer);
462
+
463
+ if (!compress) {
464
+ this._state = DEFAULT;
465
+ this.sendFrame(Sender.frame(data, options), cb);
466
+ this.dequeue();
467
+ } else {
468
+ this.dispatch(data, compress, options, cb);
469
+ }
470
+ })
471
+ .catch((err) => {
472
+ //
473
+ // `onError` is called in the next tick for the same reason that
474
+ // `callCallbacks` above is.
475
+ //
476
+ process.nextTick(onError, this, err, cb);
477
+ });
478
+ }
479
+
480
+ /**
481
+ * Dispatches a message.
482
+ *
483
+ * @param {(Buffer|String)} data The message to send
484
+ * @param {Boolean} [compress=false] Specifies whether or not to compress
485
+ * `data`
486
+ * @param {Object} options Options object
487
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
488
+ * FIN bit
489
+ * @param {Function} [options.generateMask] The function used to generate the
490
+ * masking key
491
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
492
+ * `data`
493
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
494
+ * key
495
+ * @param {Number} options.opcode The opcode
496
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
497
+ * modified
498
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
499
+ * RSV1 bit
500
+ * @param {Function} [cb] Callback
501
+ * @private
502
+ */
503
+ dispatch(data, compress, options, cb) {
504
+ if (!compress) {
505
+ this.sendFrame(Sender.frame(data, options), cb);
506
+ return;
507
+ }
508
+
509
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
510
+
511
+ this._bufferedBytes += options[kByteLength];
512
+ this._state = DEFLATING;
513
+ perMessageDeflate.compress(data, options.fin, (_, buf) => {
514
+ if (this._socket.destroyed) {
515
+ const err = new Error(
516
+ 'The socket was closed while data was being compressed'
517
+ );
518
+
519
+ callCallbacks(this, err, cb);
520
+ return;
521
+ }
522
+
523
+ this._bufferedBytes -= options[kByteLength];
524
+ this._state = DEFAULT;
525
+ options.readOnly = false;
526
+ this.sendFrame(Sender.frame(buf, options), cb);
527
+ this.dequeue();
528
+ });
529
+ }
530
+
531
+ /**
532
+ * Executes queued send operations.
533
+ *
534
+ * @private
535
+ */
536
+ dequeue() {
537
+ while (this._state === DEFAULT && this._queue.length) {
538
+ const params = this._queue.shift();
539
+
540
+ this._bufferedBytes -= params[3][kByteLength];
541
+ Reflect.apply(params[0], this, params.slice(1));
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Enqueues a send operation.
547
+ *
548
+ * @param {Array} params Send operation parameters.
549
+ * @private
550
+ */
551
+ enqueue(params) {
552
+ this._bufferedBytes += params[3][kByteLength];
553
+ this._queue.push(params);
554
+ }
555
+
556
+ /**
557
+ * Sends a frame.
558
+ *
559
+ * @param {(Buffer | String)[]} list The frame to send
560
+ * @param {Function} [cb] Callback
561
+ * @private
562
+ */
563
+ sendFrame(list, cb) {
564
+ if (list.length === 2) {
565
+ this._socket.cork();
566
+ this._socket.write(list[0]);
567
+ this._socket.write(list[1], cb);
568
+ this._socket.uncork();
569
+ } else {
570
+ this._socket.write(list[0], cb);
571
+ }
572
+ }
573
+ }
574
+
575
+ module.exports = Sender;
576
+
577
+ /**
578
+ * Calls queued callbacks with an error.
579
+ *
580
+ * @param {Sender} sender The `Sender` instance
581
+ * @param {Error} err The error to call the callbacks with
582
+ * @param {Function} [cb] The first callback
583
+ * @private
584
+ */
585
+ function callCallbacks(sender, err, cb) {
586
+ if (typeof cb === 'function') cb(err);
587
+
588
+ for (let i = 0; i < sender._queue.length; i++) {
589
+ const params = sender._queue[i];
590
+ const callback = params[params.length - 1];
591
+
592
+ if (typeof callback === 'function') callback(err);
593
+ }
594
+ }
595
+
596
+ /**
597
+ * Handles a `Sender` error.
598
+ *
599
+ * @param {Sender} sender The `Sender` instance
600
+ * @param {Error} err The error
601
+ * @param {Function} [cb] The first pending callback
602
+ * @private
603
+ */
604
+ function onError(sender, err, cb) {
605
+ callCallbacks(sender, err, cb);
606
+ sender.onerror(err);
607
+ }