isomorfeus-transport 2.0.7 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,414 +1,478 @@
1
- /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
2
-
3
- 'use strict';
4
-
5
- const net = require('net');
6
- const tls = require('tls');
7
- const { randomFillSync } = require('crypto');
8
-
9
- const PerMessageDeflate = require('./permessage-deflate');
10
- const { EMPTY_BUFFER } = require('./constants');
11
- const { isValidStatusCode } = require('./validation');
12
- const { mask: applyMask, toBuffer } = require('./buffer-util');
13
-
14
- const mask = Buffer.alloc(4);
15
-
16
- /**
17
- * HyBi Sender implementation.
18
- */
19
- class Sender {
20
- /**
21
- * Creates a Sender instance.
22
- *
23
- * @param {(net.Socket|tls.Socket)} socket The connection socket
24
- * @param {Object} [extensions] An object containing the negotiated extensions
25
- */
26
- constructor(socket, extensions) {
27
- this._extensions = extensions || {};
28
- this._socket = socket;
29
-
30
- this._firstFragment = true;
31
- this._compress = false;
32
-
33
- this._bufferedBytes = 0;
34
- this._deflating = false;
35
- this._queue = [];
36
- }
37
-
38
- /**
39
- * Frames a piece of data according to the HyBi WebSocket protocol.
40
- *
41
- * @param {Buffer} data The data to frame
42
- * @param {Object} options Options object
43
- * @param {Boolean} [options.fin=false] Specifies whether or not to set the
44
- * FIN bit
45
- * @param {Boolean} [options.mask=false] Specifies whether or not to mask
46
- * `data`
47
- * @param {Number} options.opcode The opcode
48
- * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
49
- * modified
50
- * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
51
- * RSV1 bit
52
- * @return {Buffer[]} The framed data as a list of `Buffer` instances
53
- * @public
54
- */
55
- static frame(data, options) {
56
- const merge = options.mask && options.readOnly;
57
- let offset = options.mask ? 6 : 2;
58
- let payloadLength = data.length;
59
-
60
- if (data.length >= 65536) {
61
- offset += 8;
62
- payloadLength = 127;
63
- } else if (data.length > 125) {
64
- offset += 2;
65
- payloadLength = 126;
66
- }
67
-
68
- const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
69
-
70
- target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
71
- if (options.rsv1) target[0] |= 0x40;
72
-
73
- target[1] = payloadLength;
74
-
75
- if (payloadLength === 126) {
76
- target.writeUInt16BE(data.length, 2);
77
- } else if (payloadLength === 127) {
78
- target.writeUInt32BE(0, 2);
79
- target.writeUInt32BE(data.length, 6);
80
- }
81
-
82
- if (!options.mask) return [target, data];
83
-
84
- randomFillSync(mask, 0, 4);
85
-
86
- target[1] |= 0x80;
87
- target[offset - 4] = mask[0];
88
- target[offset - 3] = mask[1];
89
- target[offset - 2] = mask[2];
90
- target[offset - 1] = mask[3];
91
-
92
- if (merge) {
93
- applyMask(data, mask, target, offset, data.length);
94
- return [target];
95
- }
96
-
97
- applyMask(data, mask, data, 0, data.length);
98
- return [target, data];
99
- }
100
-
101
- /**
102
- * Sends a close message to the other peer.
103
- *
104
- * @param {Number} [code] The status code component of the body
105
- * @param {(String|Buffer)} [data] The message component of the body
106
- * @param {Boolean} [mask=false] Specifies whether or not to mask the message
107
- * @param {Function} [cb] Callback
108
- * @public
109
- */
110
- close(code, data, mask, cb) {
111
- let buf;
112
-
113
- if (code === undefined) {
114
- buf = EMPTY_BUFFER;
115
- } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
116
- throw new TypeError('First argument must be a valid error code number');
117
- } else if (data === undefined || !data.length) {
118
- buf = Buffer.allocUnsafe(2);
119
- buf.writeUInt16BE(code, 0);
120
- } else {
121
- const length = Buffer.byteLength(data);
122
-
123
- if (length > 123) {
124
- throw new RangeError('The message must not be greater than 123 bytes');
125
- }
126
-
127
- buf = Buffer.allocUnsafe(2 + length);
128
- buf.writeUInt16BE(code, 0);
129
-
130
- if (typeof data === 'string') {
131
- buf.write(data, 2);
132
- } else {
133
- buf.set(data, 2);
134
- }
135
- }
136
-
137
- if (this._deflating) {
138
- this.enqueue([this.doClose, buf, mask, cb]);
139
- } else {
140
- this.doClose(buf, mask, cb);
141
- }
142
- }
143
-
144
- /**
145
- * Frames and sends a close message.
146
- *
147
- * @param {Buffer} data The message to send
148
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
149
- * @param {Function} [cb] Callback
150
- * @private
151
- */
152
- doClose(data, mask, cb) {
153
- this.sendFrame(
154
- Sender.frame(data, {
155
- fin: true,
156
- rsv1: false,
157
- opcode: 0x08,
158
- mask,
159
- readOnly: false
160
- }),
161
- cb
162
- );
163
- }
164
-
165
- /**
166
- * Sends a ping message to the other peer.
167
- *
168
- * @param {*} data The message to send
169
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
170
- * @param {Function} [cb] Callback
171
- * @public
172
- */
173
- ping(data, mask, cb) {
174
- const buf = toBuffer(data);
175
-
176
- if (buf.length > 125) {
177
- throw new RangeError('The data size must not be greater than 125 bytes');
178
- }
179
-
180
- if (this._deflating) {
181
- this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]);
182
- } else {
183
- this.doPing(buf, mask, toBuffer.readOnly, cb);
184
- }
185
- }
186
-
187
- /**
188
- * Frames and sends a ping message.
189
- *
190
- * @param {Buffer} data The message to send
191
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
192
- * @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
193
- * @param {Function} [cb] Callback
194
- * @private
195
- */
196
- doPing(data, mask, readOnly, cb) {
197
- this.sendFrame(
198
- Sender.frame(data, {
199
- fin: true,
200
- rsv1: false,
201
- opcode: 0x09,
202
- mask,
203
- readOnly
204
- }),
205
- cb
206
- );
207
- }
208
-
209
- /**
210
- * Sends a pong message to the other peer.
211
- *
212
- * @param {*} data The message to send
213
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
214
- * @param {Function} [cb] Callback
215
- * @public
216
- */
217
- pong(data, mask, cb) {
218
- const buf = toBuffer(data);
219
-
220
- if (buf.length > 125) {
221
- throw new RangeError('The data size must not be greater than 125 bytes');
222
- }
223
-
224
- if (this._deflating) {
225
- this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]);
226
- } else {
227
- this.doPong(buf, mask, toBuffer.readOnly, cb);
228
- }
229
- }
230
-
231
- /**
232
- * Frames and sends a pong message.
233
- *
234
- * @param {Buffer} data The message to send
235
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
236
- * @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
237
- * @param {Function} [cb] Callback
238
- * @private
239
- */
240
- doPong(data, mask, readOnly, cb) {
241
- this.sendFrame(
242
- Sender.frame(data, {
243
- fin: true,
244
- rsv1: false,
245
- opcode: 0x0a,
246
- mask,
247
- readOnly
248
- }),
249
- cb
250
- );
251
- }
252
-
253
- /**
254
- * Sends a data message to the other peer.
255
- *
256
- * @param {*} data The message to send
257
- * @param {Object} options Options object
258
- * @param {Boolean} [options.binary=false] Specifies whether `data` is binary
259
- * or text
260
- * @param {Boolean} [options.compress=false] Specifies whether or not to
261
- * compress `data`
262
- * @param {Boolean} [options.fin=false] Specifies whether the fragment is the
263
- * last one
264
- * @param {Boolean} [options.mask=false] Specifies whether or not to mask
265
- * `data`
266
- * @param {Function} [cb] Callback
267
- * @public
268
- */
269
- send(data, options, cb) {
270
- const buf = toBuffer(data);
271
- const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
272
- let opcode = options.binary ? 2 : 1;
273
- let rsv1 = options.compress;
274
-
275
- if (this._firstFragment) {
276
- this._firstFragment = false;
277
- if (rsv1 && perMessageDeflate) {
278
- rsv1 = buf.length >= perMessageDeflate._threshold;
279
- }
280
- this._compress = rsv1;
281
- } else {
282
- rsv1 = false;
283
- opcode = 0;
284
- }
285
-
286
- if (options.fin) this._firstFragment = true;
287
-
288
- if (perMessageDeflate) {
289
- const opts = {
290
- fin: options.fin,
291
- rsv1,
292
- opcode,
293
- mask: options.mask,
294
- readOnly: toBuffer.readOnly
295
- };
296
-
297
- if (this._deflating) {
298
- this.enqueue([this.dispatch, buf, this._compress, opts, cb]);
299
- } else {
300
- this.dispatch(buf, this._compress, opts, cb);
301
- }
302
- } else {
303
- this.sendFrame(
304
- Sender.frame(buf, {
305
- fin: options.fin,
306
- rsv1: false,
307
- opcode,
308
- mask: options.mask,
309
- readOnly: toBuffer.readOnly
310
- }),
311
- cb
312
- );
313
- }
314
- }
315
-
316
- /**
317
- * Dispatches a data message.
318
- *
319
- * @param {Buffer} data The message to send
320
- * @param {Boolean} [compress=false] Specifies whether or not to compress
321
- * `data`
322
- * @param {Object} options Options object
323
- * @param {Number} options.opcode The opcode
324
- * @param {Boolean} [options.fin=false] Specifies whether or not to set the
325
- * FIN bit
326
- * @param {Boolean} [options.mask=false] Specifies whether or not to mask
327
- * `data`
328
- * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
329
- * modified
330
- * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
331
- * RSV1 bit
332
- * @param {Function} [cb] Callback
333
- * @private
334
- */
335
- dispatch(data, compress, options, cb) {
336
- if (!compress) {
337
- this.sendFrame(Sender.frame(data, options), cb);
338
- return;
339
- }
340
-
341
- const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
342
-
343
- this._bufferedBytes += data.length;
344
- this._deflating = true;
345
- perMessageDeflate.compress(data, options.fin, (_, buf) => {
346
- if (this._socket.destroyed) {
347
- const err = new Error(
348
- 'The socket was closed while data was being compressed'
349
- );
350
-
351
- if (typeof cb === 'function') cb(err);
352
-
353
- for (let i = 0; i < this._queue.length; i++) {
354
- const callback = this._queue[i][4];
355
-
356
- if (typeof callback === 'function') callback(err);
357
- }
358
-
359
- return;
360
- }
361
-
362
- this._bufferedBytes -= data.length;
363
- this._deflating = false;
364
- options.readOnly = false;
365
- this.sendFrame(Sender.frame(buf, options), cb);
366
- this.dequeue();
367
- });
368
- }
369
-
370
- /**
371
- * Executes queued send operations.
372
- *
373
- * @private
374
- */
375
- dequeue() {
376
- while (!this._deflating && this._queue.length) {
377
- const params = this._queue.shift();
378
-
379
- this._bufferedBytes -= params[1].length;
380
- Reflect.apply(params[0], this, params.slice(1));
381
- }
382
- }
383
-
384
- /**
385
- * Enqueues a send operation.
386
- *
387
- * @param {Array} params Send operation parameters.
388
- * @private
389
- */
390
- enqueue(params) {
391
- this._bufferedBytes += params[1].length;
392
- this._queue.push(params);
393
- }
394
-
395
- /**
396
- * Sends a frame.
397
- *
398
- * @param {Buffer[]} list The frame to send
399
- * @param {Function} [cb] Callback
400
- * @private
401
- */
402
- sendFrame(list, cb) {
403
- if (list.length === 2) {
404
- this._socket.cork();
405
- this._socket.write(list[0]);
406
- this._socket.write(list[1], cb);
407
- this._socket.uncork();
408
- } else {
409
- this._socket.write(list[0], cb);
410
- }
411
- }
412
- }
413
-
414
- module.exports = Sender;
1
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
2
+
3
+ 'use strict';
4
+
5
+ const net = require('net');
6
+ const tls = require('tls');
7
+ const { randomFillSync } = require('crypto');
8
+
9
+ const PerMessageDeflate = require('./permessage-deflate');
10
+ const { EMPTY_BUFFER } = require('./constants');
11
+ const { isValidStatusCode } = require('./validation');
12
+ const { mask: applyMask, toBuffer } = require('./buffer-util');
13
+
14
+ const kByteLength = Symbol('kByteLength');
15
+ const maskBuffer = Buffer.alloc(4);
16
+
17
+ /**
18
+ * HyBi Sender implementation.
19
+ */
20
+ class Sender {
21
+ /**
22
+ * Creates a Sender instance.
23
+ *
24
+ * @param {(net.Socket|tls.Socket)} socket The connection socket
25
+ * @param {Object} [extensions] An object containing the negotiated extensions
26
+ * @param {Function} [generateMask] The function used to generate the masking
27
+ * key
28
+ */
29
+ constructor(socket, extensions, generateMask) {
30
+ this._extensions = extensions || {};
31
+
32
+ if (generateMask) {
33
+ this._generateMask = generateMask;
34
+ this._maskBuffer = Buffer.alloc(4);
35
+ }
36
+
37
+ this._socket = socket;
38
+
39
+ this._firstFragment = true;
40
+ this._compress = false;
41
+
42
+ this._bufferedBytes = 0;
43
+ this._deflating = false;
44
+ this._queue = [];
45
+ }
46
+
47
+ /**
48
+ * Frames a piece of data according to the HyBi WebSocket protocol.
49
+ *
50
+ * @param {(Buffer|String)} data The data to frame
51
+ * @param {Object} options Options object
52
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
53
+ * FIN bit
54
+ * @param {Function} [options.generateMask] The function used to generate the
55
+ * masking key
56
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
57
+ * `data`
58
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
59
+ * key
60
+ * @param {Number} options.opcode The opcode
61
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
62
+ * modified
63
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
64
+ * RSV1 bit
65
+ * @return {(Buffer|String)[]} The framed data
66
+ * @public
67
+ */
68
+ static frame(data, options) {
69
+ let mask;
70
+ let merge = false;
71
+ let offset = 2;
72
+ let skipMasking = false;
73
+
74
+ if (options.mask) {
75
+ mask = options.maskBuffer || maskBuffer;
76
+
77
+ if (options.generateMask) {
78
+ options.generateMask(mask);
79
+ } else {
80
+ randomFillSync(mask, 0, 4);
81
+ }
82
+
83
+ skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
84
+ offset = 6;
85
+ }
86
+
87
+ let dataLength;
88
+
89
+ if (typeof data === 'string') {
90
+ if (
91
+ (!options.mask || skipMasking) &&
92
+ options[kByteLength] !== undefined
93
+ ) {
94
+ dataLength = options[kByteLength];
95
+ } else {
96
+ data = Buffer.from(data);
97
+ dataLength = data.length;
98
+ }
99
+ } else {
100
+ dataLength = data.length;
101
+ merge = options.mask && options.readOnly && !skipMasking;
102
+ }
103
+
104
+ let payloadLength = dataLength;
105
+
106
+ if (dataLength >= 65536) {
107
+ offset += 8;
108
+ payloadLength = 127;
109
+ } else if (dataLength > 125) {
110
+ offset += 2;
111
+ payloadLength = 126;
112
+ }
113
+
114
+ const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
115
+
116
+ target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
117
+ if (options.rsv1) target[0] |= 0x40;
118
+
119
+ target[1] = payloadLength;
120
+
121
+ if (payloadLength === 126) {
122
+ target.writeUInt16BE(dataLength, 2);
123
+ } else if (payloadLength === 127) {
124
+ target[2] = target[3] = 0;
125
+ target.writeUIntBE(dataLength, 4, 6);
126
+ }
127
+
128
+ if (!options.mask) return [target, data];
129
+
130
+ target[1] |= 0x80;
131
+ target[offset - 4] = mask[0];
132
+ target[offset - 3] = mask[1];
133
+ target[offset - 2] = mask[2];
134
+ target[offset - 1] = mask[3];
135
+
136
+ if (skipMasking) return [target, data];
137
+
138
+ if (merge) {
139
+ applyMask(data, mask, target, offset, dataLength);
140
+ return [target];
141
+ }
142
+
143
+ applyMask(data, mask, data, 0, dataLength);
144
+ return [target, data];
145
+ }
146
+
147
+ /**
148
+ * Sends a close message to the other peer.
149
+ *
150
+ * @param {Number} [code] The status code component of the body
151
+ * @param {(String|Buffer)} [data] The message component of the body
152
+ * @param {Boolean} [mask=false] Specifies whether or not to mask the message
153
+ * @param {Function} [cb] Callback
154
+ * @public
155
+ */
156
+ close(code, data, mask, cb) {
157
+ let buf;
158
+
159
+ if (code === undefined) {
160
+ buf = EMPTY_BUFFER;
161
+ } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
162
+ throw new TypeError('First argument must be a valid error code number');
163
+ } else if (data === undefined || !data.length) {
164
+ buf = Buffer.allocUnsafe(2);
165
+ buf.writeUInt16BE(code, 0);
166
+ } else {
167
+ const length = Buffer.byteLength(data);
168
+
169
+ if (length > 123) {
170
+ throw new RangeError('The message must not be greater than 123 bytes');
171
+ }
172
+
173
+ buf = Buffer.allocUnsafe(2 + length);
174
+ buf.writeUInt16BE(code, 0);
175
+
176
+ if (typeof data === 'string') {
177
+ buf.write(data, 2);
178
+ } else {
179
+ buf.set(data, 2);
180
+ }
181
+ }
182
+
183
+ const options = {
184
+ [kByteLength]: buf.length,
185
+ fin: true,
186
+ generateMask: this._generateMask,
187
+ mask,
188
+ maskBuffer: this._maskBuffer,
189
+ opcode: 0x08,
190
+ readOnly: false,
191
+ rsv1: false
192
+ };
193
+
194
+ if (this._deflating) {
195
+ this.enqueue([this.dispatch, buf, false, options, cb]);
196
+ } else {
197
+ this.sendFrame(Sender.frame(buf, options), cb);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Sends a ping message to the other peer.
203
+ *
204
+ * @param {*} data The message to send
205
+ * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
206
+ * @param {Function} [cb] Callback
207
+ * @public
208
+ */
209
+ ping(data, mask, cb) {
210
+ let byteLength;
211
+ let readOnly;
212
+
213
+ if (typeof data === 'string') {
214
+ byteLength = Buffer.byteLength(data);
215
+ readOnly = false;
216
+ } else {
217
+ data = toBuffer(data);
218
+ byteLength = data.length;
219
+ readOnly = toBuffer.readOnly;
220
+ }
221
+
222
+ if (byteLength > 125) {
223
+ throw new RangeError('The data size must not be greater than 125 bytes');
224
+ }
225
+
226
+ const options = {
227
+ [kByteLength]: byteLength,
228
+ fin: true,
229
+ generateMask: this._generateMask,
230
+ mask,
231
+ maskBuffer: this._maskBuffer,
232
+ opcode: 0x09,
233
+ readOnly,
234
+ rsv1: false
235
+ };
236
+
237
+ if (this._deflating) {
238
+ this.enqueue([this.dispatch, data, false, options, cb]);
239
+ } else {
240
+ this.sendFrame(Sender.frame(data, options), cb);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Sends a pong message to the other peer.
246
+ *
247
+ * @param {*} data The message to send
248
+ * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
249
+ * @param {Function} [cb] Callback
250
+ * @public
251
+ */
252
+ pong(data, mask, cb) {
253
+ let byteLength;
254
+ let readOnly;
255
+
256
+ if (typeof data === 'string') {
257
+ byteLength = Buffer.byteLength(data);
258
+ readOnly = false;
259
+ } else {
260
+ data = toBuffer(data);
261
+ byteLength = data.length;
262
+ readOnly = toBuffer.readOnly;
263
+ }
264
+
265
+ if (byteLength > 125) {
266
+ throw new RangeError('The data size must not be greater than 125 bytes');
267
+ }
268
+
269
+ const options = {
270
+ [kByteLength]: byteLength,
271
+ fin: true,
272
+ generateMask: this._generateMask,
273
+ mask,
274
+ maskBuffer: this._maskBuffer,
275
+ opcode: 0x0a,
276
+ readOnly,
277
+ rsv1: false
278
+ };
279
+
280
+ if (this._deflating) {
281
+ this.enqueue([this.dispatch, data, false, options, cb]);
282
+ } else {
283
+ this.sendFrame(Sender.frame(data, options), cb);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Sends a data message to the other peer.
289
+ *
290
+ * @param {*} data The message to send
291
+ * @param {Object} options Options object
292
+ * @param {Boolean} [options.binary=false] Specifies whether `data` is binary
293
+ * or text
294
+ * @param {Boolean} [options.compress=false] Specifies whether or not to
295
+ * compress `data`
296
+ * @param {Boolean} [options.fin=false] Specifies whether the fragment is the
297
+ * last one
298
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
299
+ * `data`
300
+ * @param {Function} [cb] Callback
301
+ * @public
302
+ */
303
+ send(data, options, cb) {
304
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
305
+ let opcode = options.binary ? 2 : 1;
306
+ let rsv1 = options.compress;
307
+
308
+ let byteLength;
309
+ let readOnly;
310
+
311
+ if (typeof data === 'string') {
312
+ byteLength = Buffer.byteLength(data);
313
+ readOnly = false;
314
+ } else {
315
+ data = toBuffer(data);
316
+ byteLength = data.length;
317
+ readOnly = toBuffer.readOnly;
318
+ }
319
+
320
+ if (this._firstFragment) {
321
+ this._firstFragment = false;
322
+ if (
323
+ rsv1 &&
324
+ perMessageDeflate &&
325
+ perMessageDeflate.params[
326
+ perMessageDeflate._isServer
327
+ ? 'server_no_context_takeover'
328
+ : 'client_no_context_takeover'
329
+ ]
330
+ ) {
331
+ rsv1 = byteLength >= perMessageDeflate._threshold;
332
+ }
333
+ this._compress = rsv1;
334
+ } else {
335
+ rsv1 = false;
336
+ opcode = 0;
337
+ }
338
+
339
+ if (options.fin) this._firstFragment = true;
340
+
341
+ if (perMessageDeflate) {
342
+ const opts = {
343
+ [kByteLength]: byteLength,
344
+ fin: options.fin,
345
+ generateMask: this._generateMask,
346
+ mask: options.mask,
347
+ maskBuffer: this._maskBuffer,
348
+ opcode,
349
+ readOnly,
350
+ rsv1
351
+ };
352
+
353
+ if (this._deflating) {
354
+ this.enqueue([this.dispatch, data, this._compress, opts, cb]);
355
+ } else {
356
+ this.dispatch(data, this._compress, opts, cb);
357
+ }
358
+ } else {
359
+ this.sendFrame(
360
+ Sender.frame(data, {
361
+ [kByteLength]: byteLength,
362
+ fin: options.fin,
363
+ generateMask: this._generateMask,
364
+ mask: options.mask,
365
+ maskBuffer: this._maskBuffer,
366
+ opcode,
367
+ readOnly,
368
+ rsv1: false
369
+ }),
370
+ cb
371
+ );
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Dispatches a message.
377
+ *
378
+ * @param {(Buffer|String)} data The message to send
379
+ * @param {Boolean} [compress=false] Specifies whether or not to compress
380
+ * `data`
381
+ * @param {Object} options Options object
382
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
383
+ * FIN bit
384
+ * @param {Function} [options.generateMask] The function used to generate the
385
+ * masking key
386
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
387
+ * `data`
388
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
389
+ * key
390
+ * @param {Number} options.opcode The opcode
391
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
392
+ * modified
393
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
394
+ * RSV1 bit
395
+ * @param {Function} [cb] Callback
396
+ * @private
397
+ */
398
+ dispatch(data, compress, options, cb) {
399
+ if (!compress) {
400
+ this.sendFrame(Sender.frame(data, options), cb);
401
+ return;
402
+ }
403
+
404
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
405
+
406
+ this._bufferedBytes += options[kByteLength];
407
+ this._deflating = true;
408
+ perMessageDeflate.compress(data, options.fin, (_, buf) => {
409
+ if (this._socket.destroyed) {
410
+ const err = new Error(
411
+ 'The socket was closed while data was being compressed'
412
+ );
413
+
414
+ if (typeof cb === 'function') cb(err);
415
+
416
+ for (let i = 0; i < this._queue.length; i++) {
417
+ const params = this._queue[i];
418
+ const callback = params[params.length - 1];
419
+
420
+ if (typeof callback === 'function') callback(err);
421
+ }
422
+
423
+ return;
424
+ }
425
+
426
+ this._bufferedBytes -= options[kByteLength];
427
+ this._deflating = false;
428
+ options.readOnly = false;
429
+ this.sendFrame(Sender.frame(buf, options), cb);
430
+ this.dequeue();
431
+ });
432
+ }
433
+
434
+ /**
435
+ * Executes queued send operations.
436
+ *
437
+ * @private
438
+ */
439
+ dequeue() {
440
+ while (!this._deflating && this._queue.length) {
441
+ const params = this._queue.shift();
442
+
443
+ this._bufferedBytes -= params[3][kByteLength];
444
+ Reflect.apply(params[0], this, params.slice(1));
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Enqueues a send operation.
450
+ *
451
+ * @param {Array} params Send operation parameters.
452
+ * @private
453
+ */
454
+ enqueue(params) {
455
+ this._bufferedBytes += params[3][kByteLength];
456
+ this._queue.push(params);
457
+ }
458
+
459
+ /**
460
+ * Sends a frame.
461
+ *
462
+ * @param {Buffer[]} list The frame to send
463
+ * @param {Function} [cb] Callback
464
+ * @private
465
+ */
466
+ sendFrame(list, cb) {
467
+ if (list.length === 2) {
468
+ this._socket.cork();
469
+ this._socket.write(list[0]);
470
+ this._socket.write(list[1], cb);
471
+ this._socket.uncork();
472
+ } else {
473
+ this._socket.write(list[0], cb);
474
+ }
475
+ }
476
+ }
477
+
478
+ module.exports = Sender;