isomorfeus-transport 2.0.6 → 2.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }