isomorfeus-transport 2.0.7 → 2.0.11

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.
@@ -1,511 +1,511 @@
1
- 'use strict';
2
-
3
- const zlib = require('zlib');
4
-
5
- const bufferUtil = require('./buffer-util');
6
- const Limiter = require('./limiter');
7
- const { kStatusCode } = require('./constants');
8
-
9
- const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
10
- const kPerMessageDeflate = Symbol('permessage-deflate');
11
- const kTotalLength = Symbol('total-length');
12
- const kCallback = Symbol('callback');
13
- const kBuffers = Symbol('buffers');
14
- const kError = Symbol('error');
15
-
16
- //
17
- // We limit zlib concurrency, which prevents severe memory fragmentation
18
- // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
19
- // and https://github.com/websockets/ws/issues/1202
20
- //
21
- // Intentionally global; it's the global thread pool that's an issue.
22
- //
23
- let zlibLimiter;
24
-
25
- /**
26
- * permessage-deflate implementation.
27
- */
28
- class PerMessageDeflate {
29
- /**
30
- * Creates a PerMessageDeflate instance.
31
- *
32
- * @param {Object} [options] Configuration options
33
- * @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support
34
- * for, or request, a custom client window size
35
- * @param {Boolean} [options.clientNoContextTakeover=false] Advertise/
36
- * acknowledge disabling of client context takeover
37
- * @param {Number} [options.concurrencyLimit=10] The number of concurrent
38
- * calls to zlib
39
- * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
40
- * use of a custom server window size
41
- * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
42
- * disabling of server context takeover
43
- * @param {Number} [options.threshold=1024] Size (in bytes) below which
44
- * messages should not be compressed
45
- * @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on
46
- * deflate
47
- * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
48
- * inflate
49
- * @param {Boolean} [isServer=false] Create the instance in either server or
50
- * client mode
51
- * @param {Number} [maxPayload=0] The maximum allowed message length
52
- */
53
- constructor(options, isServer, maxPayload) {
54
- this._maxPayload = maxPayload | 0;
55
- this._options = options || {};
56
- this._threshold =
57
- this._options.threshold !== undefined ? this._options.threshold : 1024;
58
- this._isServer = !!isServer;
59
- this._deflate = null;
60
- this._inflate = null;
61
-
62
- this.params = null;
63
-
64
- if (!zlibLimiter) {
65
- const concurrency =
66
- this._options.concurrencyLimit !== undefined
67
- ? this._options.concurrencyLimit
68
- : 10;
69
- zlibLimiter = new Limiter(concurrency);
70
- }
71
- }
72
-
73
- /**
74
- * @type {String}
75
- */
76
- static get extensionName() {
77
- return 'permessage-deflate';
78
- }
79
-
80
- /**
81
- * Create an extension negotiation offer.
82
- *
83
- * @return {Object} Extension parameters
84
- * @public
85
- */
86
- offer() {
87
- const params = {};
88
-
89
- if (this._options.serverNoContextTakeover) {
90
- params.server_no_context_takeover = true;
91
- }
92
- if (this._options.clientNoContextTakeover) {
93
- params.client_no_context_takeover = true;
94
- }
95
- if (this._options.serverMaxWindowBits) {
96
- params.server_max_window_bits = this._options.serverMaxWindowBits;
97
- }
98
- if (this._options.clientMaxWindowBits) {
99
- params.client_max_window_bits = this._options.clientMaxWindowBits;
100
- } else if (this._options.clientMaxWindowBits == null) {
101
- params.client_max_window_bits = true;
102
- }
103
-
104
- return params;
105
- }
106
-
107
- /**
108
- * Accept an extension negotiation offer/response.
109
- *
110
- * @param {Array} configurations The extension negotiation offers/reponse
111
- * @return {Object} Accepted configuration
112
- * @public
113
- */
114
- accept(configurations) {
115
- configurations = this.normalizeParams(configurations);
116
-
117
- this.params = this._isServer
118
- ? this.acceptAsServer(configurations)
119
- : this.acceptAsClient(configurations);
120
-
121
- return this.params;
122
- }
123
-
124
- /**
125
- * Releases all resources used by the extension.
126
- *
127
- * @public
128
- */
129
- cleanup() {
130
- if (this._inflate) {
131
- this._inflate.close();
132
- this._inflate = null;
133
- }
134
-
135
- if (this._deflate) {
136
- const callback = this._deflate[kCallback];
137
-
138
- this._deflate.close();
139
- this._deflate = null;
140
-
141
- if (callback) {
142
- callback(
143
- new Error(
144
- 'The deflate stream was closed while data was being processed'
145
- )
146
- );
147
- }
148
- }
149
- }
150
-
151
- /**
152
- * Accept an extension negotiation offer.
153
- *
154
- * @param {Array} offers The extension negotiation offers
155
- * @return {Object} Accepted configuration
156
- * @private
157
- */
158
- acceptAsServer(offers) {
159
- const opts = this._options;
160
- const accepted = offers.find((params) => {
161
- if (
162
- (opts.serverNoContextTakeover === false &&
163
- params.server_no_context_takeover) ||
164
- (params.server_max_window_bits &&
165
- (opts.serverMaxWindowBits === false ||
166
- (typeof opts.serverMaxWindowBits === 'number' &&
167
- opts.serverMaxWindowBits > params.server_max_window_bits))) ||
168
- (typeof opts.clientMaxWindowBits === 'number' &&
169
- !params.client_max_window_bits)
170
- ) {
171
- return false;
172
- }
173
-
174
- return true;
175
- });
176
-
177
- if (!accepted) {
178
- throw new Error('None of the extension offers can be accepted');
179
- }
180
-
181
- if (opts.serverNoContextTakeover) {
182
- accepted.server_no_context_takeover = true;
183
- }
184
- if (opts.clientNoContextTakeover) {
185
- accepted.client_no_context_takeover = true;
186
- }
187
- if (typeof opts.serverMaxWindowBits === 'number') {
188
- accepted.server_max_window_bits = opts.serverMaxWindowBits;
189
- }
190
- if (typeof opts.clientMaxWindowBits === 'number') {
191
- accepted.client_max_window_bits = opts.clientMaxWindowBits;
192
- } else if (
193
- accepted.client_max_window_bits === true ||
194
- opts.clientMaxWindowBits === false
195
- ) {
196
- delete accepted.client_max_window_bits;
197
- }
198
-
199
- return accepted;
200
- }
201
-
202
- /**
203
- * Accept the extension negotiation response.
204
- *
205
- * @param {Array} response The extension negotiation response
206
- * @return {Object} Accepted configuration
207
- * @private
208
- */
209
- acceptAsClient(response) {
210
- const params = response[0];
211
-
212
- if (
213
- this._options.clientNoContextTakeover === false &&
214
- params.client_no_context_takeover
215
- ) {
216
- throw new Error('Unexpected parameter "client_no_context_takeover"');
217
- }
218
-
219
- if (!params.client_max_window_bits) {
220
- if (typeof this._options.clientMaxWindowBits === 'number') {
221
- params.client_max_window_bits = this._options.clientMaxWindowBits;
222
- }
223
- } else if (
224
- this._options.clientMaxWindowBits === false ||
225
- (typeof this._options.clientMaxWindowBits === 'number' &&
226
- params.client_max_window_bits > this._options.clientMaxWindowBits)
227
- ) {
228
- throw new Error(
229
- 'Unexpected or invalid parameter "client_max_window_bits"'
230
- );
231
- }
232
-
233
- return params;
234
- }
235
-
236
- /**
237
- * Normalize parameters.
238
- *
239
- * @param {Array} configurations The extension negotiation offers/reponse
240
- * @return {Array} The offers/response with normalized parameters
241
- * @private
242
- */
243
- normalizeParams(configurations) {
244
- configurations.forEach((params) => {
245
- Object.keys(params).forEach((key) => {
246
- let value = params[key];
247
-
248
- if (value.length > 1) {
249
- throw new Error(`Parameter "${key}" must have only a single value`);
250
- }
251
-
252
- value = value[0];
253
-
254
- if (key === 'client_max_window_bits') {
255
- if (value !== true) {
256
- const num = +value;
257
- if (!Number.isInteger(num) || num < 8 || num > 15) {
258
- throw new TypeError(
259
- `Invalid value for parameter "${key}": ${value}`
260
- );
261
- }
262
- value = num;
263
- } else if (!this._isServer) {
264
- throw new TypeError(
265
- `Invalid value for parameter "${key}": ${value}`
266
- );
267
- }
268
- } else if (key === 'server_max_window_bits') {
269
- const num = +value;
270
- if (!Number.isInteger(num) || num < 8 || num > 15) {
271
- throw new TypeError(
272
- `Invalid value for parameter "${key}": ${value}`
273
- );
274
- }
275
- value = num;
276
- } else if (
277
- key === 'client_no_context_takeover' ||
278
- key === 'server_no_context_takeover'
279
- ) {
280
- if (value !== true) {
281
- throw new TypeError(
282
- `Invalid value for parameter "${key}": ${value}`
283
- );
284
- }
285
- } else {
286
- throw new Error(`Unknown parameter "${key}"`);
287
- }
288
-
289
- params[key] = value;
290
- });
291
- });
292
-
293
- return configurations;
294
- }
295
-
296
- /**
297
- * Decompress data. Concurrency limited.
298
- *
299
- * @param {Buffer} data Compressed data
300
- * @param {Boolean} fin Specifies whether or not this is the last fragment
301
- * @param {Function} callback Callback
302
- * @public
303
- */
304
- decompress(data, fin, callback) {
305
- zlibLimiter.add((done) => {
306
- this._decompress(data, fin, (err, result) => {
307
- done();
308
- callback(err, result);
309
- });
310
- });
311
- }
312
-
313
- /**
314
- * Compress data. Concurrency limited.
315
- *
316
- * @param {Buffer} data Data to compress
317
- * @param {Boolean} fin Specifies whether or not this is the last fragment
318
- * @param {Function} callback Callback
319
- * @public
320
- */
321
- compress(data, fin, callback) {
322
- zlibLimiter.add((done) => {
323
- this._compress(data, fin, (err, result) => {
324
- done();
325
- callback(err, result);
326
- });
327
- });
328
- }
329
-
330
- /**
331
- * Decompress data.
332
- *
333
- * @param {Buffer} data Compressed data
334
- * @param {Boolean} fin Specifies whether or not this is the last fragment
335
- * @param {Function} callback Callback
336
- * @private
337
- */
338
- _decompress(data, fin, callback) {
339
- const endpoint = this._isServer ? 'client' : 'server';
340
-
341
- if (!this._inflate) {
342
- const key = `${endpoint}_max_window_bits`;
343
- const windowBits =
344
- typeof this.params[key] !== 'number'
345
- ? zlib.Z_DEFAULT_WINDOWBITS
346
- : this.params[key];
347
-
348
- this._inflate = zlib.createInflateRaw({
349
- ...this._options.zlibInflateOptions,
350
- windowBits
351
- });
352
- this._inflate[kPerMessageDeflate] = this;
353
- this._inflate[kTotalLength] = 0;
354
- this._inflate[kBuffers] = [];
355
- this._inflate.on('error', inflateOnError);
356
- this._inflate.on('data', inflateOnData);
357
- }
358
-
359
- this._inflate[kCallback] = callback;
360
-
361
- this._inflate.write(data);
362
- if (fin) this._inflate.write(TRAILER);
363
-
364
- this._inflate.flush(() => {
365
- const err = this._inflate[kError];
366
-
367
- if (err) {
368
- this._inflate.close();
369
- this._inflate = null;
370
- callback(err);
371
- return;
372
- }
373
-
374
- const data = bufferUtil.concat(
375
- this._inflate[kBuffers],
376
- this._inflate[kTotalLength]
377
- );
378
-
379
- if (this._inflate._readableState.endEmitted) {
380
- this._inflate.close();
381
- this._inflate = null;
382
- } else {
383
- this._inflate[kTotalLength] = 0;
384
- this._inflate[kBuffers] = [];
385
-
386
- if (fin && this.params[`${endpoint}_no_context_takeover`]) {
387
- this._inflate.reset();
388
- }
389
- }
390
-
391
- callback(null, data);
392
- });
393
- }
394
-
395
- /**
396
- * Compress data.
397
- *
398
- * @param {Buffer} data Data to compress
399
- * @param {Boolean} fin Specifies whether or not this is the last fragment
400
- * @param {Function} callback Callback
401
- * @private
402
- */
403
- _compress(data, fin, callback) {
404
- const endpoint = this._isServer ? 'server' : 'client';
405
-
406
- if (!this._deflate) {
407
- const key = `${endpoint}_max_window_bits`;
408
- const windowBits =
409
- typeof this.params[key] !== 'number'
410
- ? zlib.Z_DEFAULT_WINDOWBITS
411
- : this.params[key];
412
-
413
- this._deflate = zlib.createDeflateRaw({
414
- ...this._options.zlibDeflateOptions,
415
- windowBits
416
- });
417
-
418
- this._deflate[kTotalLength] = 0;
419
- this._deflate[kBuffers] = [];
420
-
421
- this._deflate.on('data', deflateOnData);
422
- }
423
-
424
- this._deflate[kCallback] = callback;
425
-
426
- this._deflate.write(data);
427
- this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
428
- if (!this._deflate) {
429
- //
430
- // The deflate stream was closed while data was being processed.
431
- //
432
- return;
433
- }
434
-
435
- let data = bufferUtil.concat(
436
- this._deflate[kBuffers],
437
- this._deflate[kTotalLength]
438
- );
439
-
440
- if (fin) data = data.slice(0, data.length - 4);
441
-
442
- //
443
- // Ensure that the callback will not be called again in
444
- // `PerMessageDeflate#cleanup()`.
445
- //
446
- this._deflate[kCallback] = null;
447
-
448
- this._deflate[kTotalLength] = 0;
449
- this._deflate[kBuffers] = [];
450
-
451
- if (fin && this.params[`${endpoint}_no_context_takeover`]) {
452
- this._deflate.reset();
453
- }
454
-
455
- callback(null, data);
456
- });
457
- }
458
- }
459
-
460
- module.exports = PerMessageDeflate;
461
-
462
- /**
463
- * The listener of the `zlib.DeflateRaw` stream `'data'` event.
464
- *
465
- * @param {Buffer} chunk A chunk of data
466
- * @private
467
- */
468
- function deflateOnData(chunk) {
469
- this[kBuffers].push(chunk);
470
- this[kTotalLength] += chunk.length;
471
- }
472
-
473
- /**
474
- * The listener of the `zlib.InflateRaw` stream `'data'` event.
475
- *
476
- * @param {Buffer} chunk A chunk of data
477
- * @private
478
- */
479
- function inflateOnData(chunk) {
480
- this[kTotalLength] += chunk.length;
481
-
482
- if (
483
- this[kPerMessageDeflate]._maxPayload < 1 ||
484
- this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
485
- ) {
486
- this[kBuffers].push(chunk);
487
- return;
488
- }
489
-
490
- this[kError] = new RangeError('Max payload size exceeded');
491
- this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
492
- this[kError][kStatusCode] = 1009;
493
- this.removeListener('data', inflateOnData);
494
- this.reset();
495
- }
496
-
497
- /**
498
- * The listener of the `zlib.InflateRaw` stream `'error'` event.
499
- *
500
- * @param {Error} err The emitted error
501
- * @private
502
- */
503
- function inflateOnError(err) {
504
- //
505
- // There is no need to call `Zlib#close()` as the handle is automatically
506
- // closed when an error is emitted.
507
- //
508
- this[kPerMessageDeflate]._inflate = null;
509
- err[kStatusCode] = 1007;
510
- this[kCallback](err);
511
- }
1
+ 'use strict';
2
+
3
+ const zlib = require('zlib');
4
+
5
+ const bufferUtil = require('./buffer-util');
6
+ const Limiter = require('./limiter');
7
+ const { kStatusCode } = require('./constants');
8
+
9
+ const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
10
+ const kPerMessageDeflate = Symbol('permessage-deflate');
11
+ const kTotalLength = Symbol('total-length');
12
+ const kCallback = Symbol('callback');
13
+ const kBuffers = Symbol('buffers');
14
+ const kError = Symbol('error');
15
+
16
+ //
17
+ // We limit zlib concurrency, which prevents severe memory fragmentation
18
+ // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
19
+ // and https://github.com/websockets/ws/issues/1202
20
+ //
21
+ // Intentionally global; it's the global thread pool that's an issue.
22
+ //
23
+ let zlibLimiter;
24
+
25
+ /**
26
+ * permessage-deflate implementation.
27
+ */
28
+ class PerMessageDeflate {
29
+ /**
30
+ * Creates a PerMessageDeflate instance.
31
+ *
32
+ * @param {Object} [options] Configuration options
33
+ * @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support
34
+ * for, or request, a custom client window size
35
+ * @param {Boolean} [options.clientNoContextTakeover=false] Advertise/
36
+ * acknowledge disabling of client context takeover
37
+ * @param {Number} [options.concurrencyLimit=10] The number of concurrent
38
+ * calls to zlib
39
+ * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
40
+ * use of a custom server window size
41
+ * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
42
+ * disabling of server context takeover
43
+ * @param {Number} [options.threshold=1024] Size (in bytes) below which
44
+ * messages should not be compressed if context takeover is disabled
45
+ * @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on
46
+ * deflate
47
+ * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
48
+ * inflate
49
+ * @param {Boolean} [isServer=false] Create the instance in either server or
50
+ * client mode
51
+ * @param {Number} [maxPayload=0] The maximum allowed message length
52
+ */
53
+ constructor(options, isServer, maxPayload) {
54
+ this._maxPayload = maxPayload | 0;
55
+ this._options = options || {};
56
+ this._threshold =
57
+ this._options.threshold !== undefined ? this._options.threshold : 1024;
58
+ this._isServer = !!isServer;
59
+ this._deflate = null;
60
+ this._inflate = null;
61
+
62
+ this.params = null;
63
+
64
+ if (!zlibLimiter) {
65
+ const concurrency =
66
+ this._options.concurrencyLimit !== undefined
67
+ ? this._options.concurrencyLimit
68
+ : 10;
69
+ zlibLimiter = new Limiter(concurrency);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * @type {String}
75
+ */
76
+ static get extensionName() {
77
+ return 'permessage-deflate';
78
+ }
79
+
80
+ /**
81
+ * Create an extension negotiation offer.
82
+ *
83
+ * @return {Object} Extension parameters
84
+ * @public
85
+ */
86
+ offer() {
87
+ const params = {};
88
+
89
+ if (this._options.serverNoContextTakeover) {
90
+ params.server_no_context_takeover = true;
91
+ }
92
+ if (this._options.clientNoContextTakeover) {
93
+ params.client_no_context_takeover = true;
94
+ }
95
+ if (this._options.serverMaxWindowBits) {
96
+ params.server_max_window_bits = this._options.serverMaxWindowBits;
97
+ }
98
+ if (this._options.clientMaxWindowBits) {
99
+ params.client_max_window_bits = this._options.clientMaxWindowBits;
100
+ } else if (this._options.clientMaxWindowBits == null) {
101
+ params.client_max_window_bits = true;
102
+ }
103
+
104
+ return params;
105
+ }
106
+
107
+ /**
108
+ * Accept an extension negotiation offer/response.
109
+ *
110
+ * @param {Array} configurations The extension negotiation offers/reponse
111
+ * @return {Object} Accepted configuration
112
+ * @public
113
+ */
114
+ accept(configurations) {
115
+ configurations = this.normalizeParams(configurations);
116
+
117
+ this.params = this._isServer
118
+ ? this.acceptAsServer(configurations)
119
+ : this.acceptAsClient(configurations);
120
+
121
+ return this.params;
122
+ }
123
+
124
+ /**
125
+ * Releases all resources used by the extension.
126
+ *
127
+ * @public
128
+ */
129
+ cleanup() {
130
+ if (this._inflate) {
131
+ this._inflate.close();
132
+ this._inflate = null;
133
+ }
134
+
135
+ if (this._deflate) {
136
+ const callback = this._deflate[kCallback];
137
+
138
+ this._deflate.close();
139
+ this._deflate = null;
140
+
141
+ if (callback) {
142
+ callback(
143
+ new Error(
144
+ 'The deflate stream was closed while data was being processed'
145
+ )
146
+ );
147
+ }
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Accept an extension negotiation offer.
153
+ *
154
+ * @param {Array} offers The extension negotiation offers
155
+ * @return {Object} Accepted configuration
156
+ * @private
157
+ */
158
+ acceptAsServer(offers) {
159
+ const opts = this._options;
160
+ const accepted = offers.find((params) => {
161
+ if (
162
+ (opts.serverNoContextTakeover === false &&
163
+ params.server_no_context_takeover) ||
164
+ (params.server_max_window_bits &&
165
+ (opts.serverMaxWindowBits === false ||
166
+ (typeof opts.serverMaxWindowBits === 'number' &&
167
+ opts.serverMaxWindowBits > params.server_max_window_bits))) ||
168
+ (typeof opts.clientMaxWindowBits === 'number' &&
169
+ !params.client_max_window_bits)
170
+ ) {
171
+ return false;
172
+ }
173
+
174
+ return true;
175
+ });
176
+
177
+ if (!accepted) {
178
+ throw new Error('None of the extension offers can be accepted');
179
+ }
180
+
181
+ if (opts.serverNoContextTakeover) {
182
+ accepted.server_no_context_takeover = true;
183
+ }
184
+ if (opts.clientNoContextTakeover) {
185
+ accepted.client_no_context_takeover = true;
186
+ }
187
+ if (typeof opts.serverMaxWindowBits === 'number') {
188
+ accepted.server_max_window_bits = opts.serverMaxWindowBits;
189
+ }
190
+ if (typeof opts.clientMaxWindowBits === 'number') {
191
+ accepted.client_max_window_bits = opts.clientMaxWindowBits;
192
+ } else if (
193
+ accepted.client_max_window_bits === true ||
194
+ opts.clientMaxWindowBits === false
195
+ ) {
196
+ delete accepted.client_max_window_bits;
197
+ }
198
+
199
+ return accepted;
200
+ }
201
+
202
+ /**
203
+ * Accept the extension negotiation response.
204
+ *
205
+ * @param {Array} response The extension negotiation response
206
+ * @return {Object} Accepted configuration
207
+ * @private
208
+ */
209
+ acceptAsClient(response) {
210
+ const params = response[0];
211
+
212
+ if (
213
+ this._options.clientNoContextTakeover === false &&
214
+ params.client_no_context_takeover
215
+ ) {
216
+ throw new Error('Unexpected parameter "client_no_context_takeover"');
217
+ }
218
+
219
+ if (!params.client_max_window_bits) {
220
+ if (typeof this._options.clientMaxWindowBits === 'number') {
221
+ params.client_max_window_bits = this._options.clientMaxWindowBits;
222
+ }
223
+ } else if (
224
+ this._options.clientMaxWindowBits === false ||
225
+ (typeof this._options.clientMaxWindowBits === 'number' &&
226
+ params.client_max_window_bits > this._options.clientMaxWindowBits)
227
+ ) {
228
+ throw new Error(
229
+ 'Unexpected or invalid parameter "client_max_window_bits"'
230
+ );
231
+ }
232
+
233
+ return params;
234
+ }
235
+
236
+ /**
237
+ * Normalize parameters.
238
+ *
239
+ * @param {Array} configurations The extension negotiation offers/reponse
240
+ * @return {Array} The offers/response with normalized parameters
241
+ * @private
242
+ */
243
+ normalizeParams(configurations) {
244
+ configurations.forEach((params) => {
245
+ Object.keys(params).forEach((key) => {
246
+ let value = params[key];
247
+
248
+ if (value.length > 1) {
249
+ throw new Error(`Parameter "${key}" must have only a single value`);
250
+ }
251
+
252
+ value = value[0];
253
+
254
+ if (key === 'client_max_window_bits') {
255
+ if (value !== true) {
256
+ const num = +value;
257
+ if (!Number.isInteger(num) || num < 8 || num > 15) {
258
+ throw new TypeError(
259
+ `Invalid value for parameter "${key}": ${value}`
260
+ );
261
+ }
262
+ value = num;
263
+ } else if (!this._isServer) {
264
+ throw new TypeError(
265
+ `Invalid value for parameter "${key}": ${value}`
266
+ );
267
+ }
268
+ } else if (key === 'server_max_window_bits') {
269
+ const num = +value;
270
+ if (!Number.isInteger(num) || num < 8 || num > 15) {
271
+ throw new TypeError(
272
+ `Invalid value for parameter "${key}": ${value}`
273
+ );
274
+ }
275
+ value = num;
276
+ } else if (
277
+ key === 'client_no_context_takeover' ||
278
+ key === 'server_no_context_takeover'
279
+ ) {
280
+ if (value !== true) {
281
+ throw new TypeError(
282
+ `Invalid value for parameter "${key}": ${value}`
283
+ );
284
+ }
285
+ } else {
286
+ throw new Error(`Unknown parameter "${key}"`);
287
+ }
288
+
289
+ params[key] = value;
290
+ });
291
+ });
292
+
293
+ return configurations;
294
+ }
295
+
296
+ /**
297
+ * Decompress data. Concurrency limited.
298
+ *
299
+ * @param {Buffer} data Compressed data
300
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
301
+ * @param {Function} callback Callback
302
+ * @public
303
+ */
304
+ decompress(data, fin, callback) {
305
+ zlibLimiter.add((done) => {
306
+ this._decompress(data, fin, (err, result) => {
307
+ done();
308
+ callback(err, result);
309
+ });
310
+ });
311
+ }
312
+
313
+ /**
314
+ * Compress data. Concurrency limited.
315
+ *
316
+ * @param {(Buffer|String)} data Data to compress
317
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
318
+ * @param {Function} callback Callback
319
+ * @public
320
+ */
321
+ compress(data, fin, callback) {
322
+ zlibLimiter.add((done) => {
323
+ this._compress(data, fin, (err, result) => {
324
+ done();
325
+ callback(err, result);
326
+ });
327
+ });
328
+ }
329
+
330
+ /**
331
+ * Decompress data.
332
+ *
333
+ * @param {Buffer} data Compressed data
334
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
335
+ * @param {Function} callback Callback
336
+ * @private
337
+ */
338
+ _decompress(data, fin, callback) {
339
+ const endpoint = this._isServer ? 'client' : 'server';
340
+
341
+ if (!this._inflate) {
342
+ const key = `${endpoint}_max_window_bits`;
343
+ const windowBits =
344
+ typeof this.params[key] !== 'number'
345
+ ? zlib.Z_DEFAULT_WINDOWBITS
346
+ : this.params[key];
347
+
348
+ this._inflate = zlib.createInflateRaw({
349
+ ...this._options.zlibInflateOptions,
350
+ windowBits
351
+ });
352
+ this._inflate[kPerMessageDeflate] = this;
353
+ this._inflate[kTotalLength] = 0;
354
+ this._inflate[kBuffers] = [];
355
+ this._inflate.on('error', inflateOnError);
356
+ this._inflate.on('data', inflateOnData);
357
+ }
358
+
359
+ this._inflate[kCallback] = callback;
360
+
361
+ this._inflate.write(data);
362
+ if (fin) this._inflate.write(TRAILER);
363
+
364
+ this._inflate.flush(() => {
365
+ const err = this._inflate[kError];
366
+
367
+ if (err) {
368
+ this._inflate.close();
369
+ this._inflate = null;
370
+ callback(err);
371
+ return;
372
+ }
373
+
374
+ const data = bufferUtil.concat(
375
+ this._inflate[kBuffers],
376
+ this._inflate[kTotalLength]
377
+ );
378
+
379
+ if (this._inflate._readableState.endEmitted) {
380
+ this._inflate.close();
381
+ this._inflate = null;
382
+ } else {
383
+ this._inflate[kTotalLength] = 0;
384
+ this._inflate[kBuffers] = [];
385
+
386
+ if (fin && this.params[`${endpoint}_no_context_takeover`]) {
387
+ this._inflate.reset();
388
+ }
389
+ }
390
+
391
+ callback(null, data);
392
+ });
393
+ }
394
+
395
+ /**
396
+ * Compress data.
397
+ *
398
+ * @param {(Buffer|String)} data Data to compress
399
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
400
+ * @param {Function} callback Callback
401
+ * @private
402
+ */
403
+ _compress(data, fin, callback) {
404
+ const endpoint = this._isServer ? 'server' : 'client';
405
+
406
+ if (!this._deflate) {
407
+ const key = `${endpoint}_max_window_bits`;
408
+ const windowBits =
409
+ typeof this.params[key] !== 'number'
410
+ ? zlib.Z_DEFAULT_WINDOWBITS
411
+ : this.params[key];
412
+
413
+ this._deflate = zlib.createDeflateRaw({
414
+ ...this._options.zlibDeflateOptions,
415
+ windowBits
416
+ });
417
+
418
+ this._deflate[kTotalLength] = 0;
419
+ this._deflate[kBuffers] = [];
420
+
421
+ this._deflate.on('data', deflateOnData);
422
+ }
423
+
424
+ this._deflate[kCallback] = callback;
425
+
426
+ this._deflate.write(data);
427
+ this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
428
+ if (!this._deflate) {
429
+ //
430
+ // The deflate stream was closed while data was being processed.
431
+ //
432
+ return;
433
+ }
434
+
435
+ let data = bufferUtil.concat(
436
+ this._deflate[kBuffers],
437
+ this._deflate[kTotalLength]
438
+ );
439
+
440
+ if (fin) data = data.slice(0, data.length - 4);
441
+
442
+ //
443
+ // Ensure that the callback will not be called again in
444
+ // `PerMessageDeflate#cleanup()`.
445
+ //
446
+ this._deflate[kCallback] = null;
447
+
448
+ this._deflate[kTotalLength] = 0;
449
+ this._deflate[kBuffers] = [];
450
+
451
+ if (fin && this.params[`${endpoint}_no_context_takeover`]) {
452
+ this._deflate.reset();
453
+ }
454
+
455
+ callback(null, data);
456
+ });
457
+ }
458
+ }
459
+
460
+ module.exports = PerMessageDeflate;
461
+
462
+ /**
463
+ * The listener of the `zlib.DeflateRaw` stream `'data'` event.
464
+ *
465
+ * @param {Buffer} chunk A chunk of data
466
+ * @private
467
+ */
468
+ function deflateOnData(chunk) {
469
+ this[kBuffers].push(chunk);
470
+ this[kTotalLength] += chunk.length;
471
+ }
472
+
473
+ /**
474
+ * The listener of the `zlib.InflateRaw` stream `'data'` event.
475
+ *
476
+ * @param {Buffer} chunk A chunk of data
477
+ * @private
478
+ */
479
+ function inflateOnData(chunk) {
480
+ this[kTotalLength] += chunk.length;
481
+
482
+ if (
483
+ this[kPerMessageDeflate]._maxPayload < 1 ||
484
+ this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
485
+ ) {
486
+ this[kBuffers].push(chunk);
487
+ return;
488
+ }
489
+
490
+ this[kError] = new RangeError('Max payload size exceeded');
491
+ this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
492
+ this[kError][kStatusCode] = 1009;
493
+ this.removeListener('data', inflateOnData);
494
+ this.reset();
495
+ }
496
+
497
+ /**
498
+ * The listener of the `zlib.InflateRaw` stream `'error'` event.
499
+ *
500
+ * @param {Error} err The emitted error
501
+ * @private
502
+ */
503
+ function inflateOnError(err) {
504
+ //
505
+ // There is no need to call `Zlib#close()` as the handle is automatically
506
+ // closed when an error is emitted.
507
+ //
508
+ this[kPerMessageDeflate]._inflate = null;
509
+ err[kStatusCode] = 1007;
510
+ this[kCallback](err);
511
+ }