isomorfeus-transport 1.0.0.zeta24 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -21
  3. data/README.md +27 -36
  4. data/lib/isomorfeus/transport/client_processor.rb +35 -35
  5. data/lib/isomorfeus/transport/config.rb +182 -166
  6. data/lib/isomorfeus/transport/hamster_session_store.rb +96 -0
  7. data/lib/isomorfeus/transport/handler/authentication_handler.rb +70 -70
  8. data/lib/isomorfeus/transport/imports.rb +9 -0
  9. data/lib/isomorfeus/transport/middlewares.rb +13 -13
  10. data/lib/isomorfeus/transport/rack_middleware.rb +59 -55
  11. data/lib/isomorfeus/transport/request_agent.rb +34 -34
  12. data/lib/isomorfeus/transport/response_agent.rb +23 -23
  13. data/lib/isomorfeus/transport/server_processor.rb +129 -129
  14. data/lib/isomorfeus/transport/server_socket_processor.rb +54 -54
  15. data/lib/isomorfeus/transport/ssr_login.rb +28 -28
  16. data/lib/isomorfeus/transport/version.rb +5 -5
  17. data/lib/isomorfeus/transport/{websocket.rb → websocket_client.rb} +123 -123
  18. data/lib/isomorfeus/transport.rb +200 -196
  19. data/lib/isomorfeus-transport.rb +70 -61
  20. data/lib/lucid_authentication/mixin.rb +122 -122
  21. data/lib/lucid_channel/base.rb +8 -9
  22. data/lib/lucid_channel/mixin.rb +105 -105
  23. data/lib/lucid_handler/base.rb +8 -9
  24. data/lib/lucid_handler/mixin.rb +27 -27
  25. data/node_modules/.package-lock.json +27 -0
  26. data/node_modules/ws/LICENSE +19 -0
  27. data/node_modules/ws/README.md +496 -0
  28. data/node_modules/ws/browser.js +8 -0
  29. data/node_modules/ws/index.js +13 -0
  30. data/node_modules/ws/lib/buffer-util.js +126 -0
  31. data/node_modules/ws/lib/constants.js +12 -0
  32. data/node_modules/ws/lib/event-target.js +266 -0
  33. data/node_modules/ws/lib/extension.js +203 -0
  34. data/node_modules/ws/lib/limiter.js +55 -0
  35. data/node_modules/ws/lib/permessage-deflate.js +511 -0
  36. data/node_modules/ws/lib/receiver.js +612 -0
  37. data/node_modules/ws/lib/sender.js +414 -0
  38. data/node_modules/ws/lib/stream.js +180 -0
  39. data/node_modules/ws/lib/subprotocol.js +62 -0
  40. data/node_modules/ws/lib/validation.js +124 -0
  41. data/node_modules/ws/lib/websocket-server.js +485 -0
  42. data/node_modules/ws/lib/websocket.js +1144 -0
  43. data/node_modules/ws/package.json +61 -0
  44. data/node_modules/ws/wrapper.mjs +8 -0
  45. data/package.json +6 -0
  46. metadata +76 -54
  47. data/lib/isomorfeus/transport/dbm_session_store.rb +0 -51
@@ -0,0 +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
+ }