isomorfeus-transport 1.0.0.zeta23 → 2.0.0.rc2

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 -30
  5. data/lib/isomorfeus/transport/config.rb +182 -158
  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 -101
  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 -213
  19. data/lib/isomorfeus-transport.rb +70 -62
  20. data/lib/lucid_authentication/mixin.rb +122 -124
  21. data/lib/lucid_channel/base.rb +8 -11
  22. data/lib/lucid_channel/mixin.rb +105 -50
  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,1144 @@
1
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
2
+
3
+ 'use strict';
4
+
5
+ const EventEmitter = require('events');
6
+ const https = require('https');
7
+ const http = require('http');
8
+ const net = require('net');
9
+ const tls = require('tls');
10
+ const { randomBytes, createHash } = require('crypto');
11
+ const { Readable } = require('stream');
12
+ const { URL } = require('url');
13
+
14
+ const PerMessageDeflate = require('./permessage-deflate');
15
+ const Receiver = require('./receiver');
16
+ const Sender = require('./sender');
17
+ const {
18
+ BINARY_TYPES,
19
+ EMPTY_BUFFER,
20
+ GUID,
21
+ kForOnEventAttribute,
22
+ kListener,
23
+ kStatusCode,
24
+ kWebSocket,
25
+ NOOP
26
+ } = require('./constants');
27
+ const {
28
+ EventTarget: { addEventListener, removeEventListener }
29
+ } = require('./event-target');
30
+ const { format, parse } = require('./extension');
31
+ const { toBuffer } = require('./buffer-util');
32
+
33
+ const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
34
+ const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
35
+ const protocolVersions = [8, 13];
36
+ const closeTimeout = 30 * 1000;
37
+
38
+ /**
39
+ * Class representing a WebSocket.
40
+ *
41
+ * @extends EventEmitter
42
+ */
43
+ class WebSocket extends EventEmitter {
44
+ /**
45
+ * Create a new `WebSocket`.
46
+ *
47
+ * @param {(String|URL)} address The URL to which to connect
48
+ * @param {(String|String[])} [protocols] The subprotocols
49
+ * @param {Object} [options] Connection options
50
+ */
51
+ constructor(address, protocols, options) {
52
+ super();
53
+
54
+ this._binaryType = BINARY_TYPES[0];
55
+ this._closeCode = 1006;
56
+ this._closeFrameReceived = false;
57
+ this._closeFrameSent = false;
58
+ this._closeMessage = EMPTY_BUFFER;
59
+ this._closeTimer = null;
60
+ this._extensions = {};
61
+ this._protocol = '';
62
+ this._readyState = WebSocket.CONNECTING;
63
+ this._receiver = null;
64
+ this._sender = null;
65
+ this._socket = null;
66
+
67
+ if (address !== null) {
68
+ this._bufferedAmount = 0;
69
+ this._isServer = false;
70
+ this._redirects = 0;
71
+
72
+ if (protocols === undefined) {
73
+ protocols = [];
74
+ } else if (!Array.isArray(protocols)) {
75
+ if (typeof protocols === 'object' && protocols !== null) {
76
+ options = protocols;
77
+ protocols = [];
78
+ } else {
79
+ protocols = [protocols];
80
+ }
81
+ }
82
+
83
+ initAsClient(this, address, protocols, options);
84
+ } else {
85
+ this._isServer = true;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * This deviates from the WHATWG interface since ws doesn't support the
91
+ * required default "blob" type (instead we define a custom "nodebuffer"
92
+ * type).
93
+ *
94
+ * @type {String}
95
+ */
96
+ get binaryType() {
97
+ return this._binaryType;
98
+ }
99
+
100
+ set binaryType(type) {
101
+ if (!BINARY_TYPES.includes(type)) return;
102
+
103
+ this._binaryType = type;
104
+
105
+ //
106
+ // Allow to change `binaryType` on the fly.
107
+ //
108
+ if (this._receiver) this._receiver._binaryType = type;
109
+ }
110
+
111
+ /**
112
+ * @type {Number}
113
+ */
114
+ get bufferedAmount() {
115
+ if (!this._socket) return this._bufferedAmount;
116
+
117
+ return this._socket._writableState.length + this._sender._bufferedBytes;
118
+ }
119
+
120
+ /**
121
+ * @type {String}
122
+ */
123
+ get extensions() {
124
+ return Object.keys(this._extensions).join();
125
+ }
126
+
127
+ /**
128
+ * @type {Function}
129
+ */
130
+ /* istanbul ignore next */
131
+ get onclose() {
132
+ return null;
133
+ }
134
+
135
+ /**
136
+ * @type {Function}
137
+ */
138
+ /* istanbul ignore next */
139
+ get onerror() {
140
+ return null;
141
+ }
142
+
143
+ /**
144
+ * @type {Function}
145
+ */
146
+ /* istanbul ignore next */
147
+ get onopen() {
148
+ return null;
149
+ }
150
+
151
+ /**
152
+ * @type {Function}
153
+ */
154
+ /* istanbul ignore next */
155
+ get onmessage() {
156
+ return null;
157
+ }
158
+
159
+ /**
160
+ * @type {String}
161
+ */
162
+ get protocol() {
163
+ return this._protocol;
164
+ }
165
+
166
+ /**
167
+ * @type {Number}
168
+ */
169
+ get readyState() {
170
+ return this._readyState;
171
+ }
172
+
173
+ /**
174
+ * @type {String}
175
+ */
176
+ get url() {
177
+ return this._url;
178
+ }
179
+
180
+ /**
181
+ * Set up the socket and the internal resources.
182
+ *
183
+ * @param {(net.Socket|tls.Socket)} socket The network socket between the
184
+ * server and client
185
+ * @param {Buffer} head The first packet of the upgraded stream
186
+ * @param {Object} options Options object
187
+ * @param {Number} [options.maxPayload=0] The maximum allowed message size
188
+ * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
189
+ * not to skip UTF-8 validation for text and close messages
190
+ * @private
191
+ */
192
+ setSocket(socket, head, options) {
193
+ const receiver = new Receiver({
194
+ binaryType: this.binaryType,
195
+ extensions: this._extensions,
196
+ isServer: this._isServer,
197
+ maxPayload: options.maxPayload,
198
+ skipUTF8Validation: options.skipUTF8Validation
199
+ });
200
+
201
+ this._sender = new Sender(socket, this._extensions);
202
+ this._receiver = receiver;
203
+ this._socket = socket;
204
+
205
+ receiver[kWebSocket] = this;
206
+ socket[kWebSocket] = this;
207
+
208
+ receiver.on('conclude', receiverOnConclude);
209
+ receiver.on('drain', receiverOnDrain);
210
+ receiver.on('error', receiverOnError);
211
+ receiver.on('message', receiverOnMessage);
212
+ receiver.on('ping', receiverOnPing);
213
+ receiver.on('pong', receiverOnPong);
214
+
215
+ socket.setTimeout(0);
216
+ socket.setNoDelay();
217
+
218
+ if (head.length > 0) socket.unshift(head);
219
+
220
+ socket.on('close', socketOnClose);
221
+ socket.on('data', socketOnData);
222
+ socket.on('end', socketOnEnd);
223
+ socket.on('error', socketOnError);
224
+
225
+ this._readyState = WebSocket.OPEN;
226
+ this.emit('open');
227
+ }
228
+
229
+ /**
230
+ * Emit the `'close'` event.
231
+ *
232
+ * @private
233
+ */
234
+ emitClose() {
235
+ if (!this._socket) {
236
+ this._readyState = WebSocket.CLOSED;
237
+ this.emit('close', this._closeCode, this._closeMessage);
238
+ return;
239
+ }
240
+
241
+ if (this._extensions[PerMessageDeflate.extensionName]) {
242
+ this._extensions[PerMessageDeflate.extensionName].cleanup();
243
+ }
244
+
245
+ this._receiver.removeAllListeners();
246
+ this._readyState = WebSocket.CLOSED;
247
+ this.emit('close', this._closeCode, this._closeMessage);
248
+ }
249
+
250
+ /**
251
+ * Start a closing handshake.
252
+ *
253
+ * +----------+ +-----------+ +----------+
254
+ * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
255
+ * | +----------+ +-----------+ +----------+ |
256
+ * +----------+ +-----------+ |
257
+ * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
258
+ * +----------+ +-----------+ |
259
+ * | | | +---+ |
260
+ * +------------------------+-->|fin| - - - -
261
+ * | +---+ | +---+
262
+ * - - - - -|fin|<---------------------+
263
+ * +---+
264
+ *
265
+ * @param {Number} [code] Status code explaining why the connection is closing
266
+ * @param {(String|Buffer)} [data] The reason why the connection is
267
+ * closing
268
+ * @public
269
+ */
270
+ close(code, data) {
271
+ if (this.readyState === WebSocket.CLOSED) return;
272
+ if (this.readyState === WebSocket.CONNECTING) {
273
+ const msg = 'WebSocket was closed before the connection was established';
274
+ return abortHandshake(this, this._req, msg);
275
+ }
276
+
277
+ if (this.readyState === WebSocket.CLOSING) {
278
+ if (
279
+ this._closeFrameSent &&
280
+ (this._closeFrameReceived || this._receiver._writableState.errorEmitted)
281
+ ) {
282
+ this._socket.end();
283
+ }
284
+
285
+ return;
286
+ }
287
+
288
+ this._readyState = WebSocket.CLOSING;
289
+ this._sender.close(code, data, !this._isServer, (err) => {
290
+ //
291
+ // This error is handled by the `'error'` listener on the socket. We only
292
+ // want to know if the close frame has been sent here.
293
+ //
294
+ if (err) return;
295
+
296
+ this._closeFrameSent = true;
297
+
298
+ if (
299
+ this._closeFrameReceived ||
300
+ this._receiver._writableState.errorEmitted
301
+ ) {
302
+ this._socket.end();
303
+ }
304
+ });
305
+
306
+ //
307
+ // Specify a timeout for the closing handshake to complete.
308
+ //
309
+ this._closeTimer = setTimeout(
310
+ this._socket.destroy.bind(this._socket),
311
+ closeTimeout
312
+ );
313
+ }
314
+
315
+ /**
316
+ * Send a ping.
317
+ *
318
+ * @param {*} [data] The data to send
319
+ * @param {Boolean} [mask] Indicates whether or not to mask `data`
320
+ * @param {Function} [cb] Callback which is executed when the ping is sent
321
+ * @public
322
+ */
323
+ ping(data, mask, cb) {
324
+ if (this.readyState === WebSocket.CONNECTING) {
325
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
326
+ }
327
+
328
+ if (typeof data === 'function') {
329
+ cb = data;
330
+ data = mask = undefined;
331
+ } else if (typeof mask === 'function') {
332
+ cb = mask;
333
+ mask = undefined;
334
+ }
335
+
336
+ if (typeof data === 'number') data = data.toString();
337
+
338
+ if (this.readyState !== WebSocket.OPEN) {
339
+ sendAfterClose(this, data, cb);
340
+ return;
341
+ }
342
+
343
+ if (mask === undefined) mask = !this._isServer;
344
+ this._sender.ping(data || EMPTY_BUFFER, mask, cb);
345
+ }
346
+
347
+ /**
348
+ * Send a pong.
349
+ *
350
+ * @param {*} [data] The data to send
351
+ * @param {Boolean} [mask] Indicates whether or not to mask `data`
352
+ * @param {Function} [cb] Callback which is executed when the pong is sent
353
+ * @public
354
+ */
355
+ pong(data, mask, cb) {
356
+ if (this.readyState === WebSocket.CONNECTING) {
357
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
358
+ }
359
+
360
+ if (typeof data === 'function') {
361
+ cb = data;
362
+ data = mask = undefined;
363
+ } else if (typeof mask === 'function') {
364
+ cb = mask;
365
+ mask = undefined;
366
+ }
367
+
368
+ if (typeof data === 'number') data = data.toString();
369
+
370
+ if (this.readyState !== WebSocket.OPEN) {
371
+ sendAfterClose(this, data, cb);
372
+ return;
373
+ }
374
+
375
+ if (mask === undefined) mask = !this._isServer;
376
+ this._sender.pong(data || EMPTY_BUFFER, mask, cb);
377
+ }
378
+
379
+ /**
380
+ * Send a data message.
381
+ *
382
+ * @param {*} data The message to send
383
+ * @param {Object} [options] Options object
384
+ * @param {Boolean} [options.binary] Specifies whether `data` is binary or
385
+ * text
386
+ * @param {Boolean} [options.compress] Specifies whether or not to compress
387
+ * `data`
388
+ * @param {Boolean} [options.fin=true] Specifies whether the fragment is the
389
+ * last one
390
+ * @param {Boolean} [options.mask] Specifies whether or not to mask `data`
391
+ * @param {Function} [cb] Callback which is executed when data is written out
392
+ * @public
393
+ */
394
+ send(data, options, cb) {
395
+ if (this.readyState === WebSocket.CONNECTING) {
396
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
397
+ }
398
+
399
+ if (typeof options === 'function') {
400
+ cb = options;
401
+ options = {};
402
+ }
403
+
404
+ if (typeof data === 'number') data = data.toString();
405
+
406
+ if (this.readyState !== WebSocket.OPEN) {
407
+ sendAfterClose(this, data, cb);
408
+ return;
409
+ }
410
+
411
+ const opts = {
412
+ binary: typeof data !== 'string',
413
+ mask: !this._isServer,
414
+ compress: true,
415
+ fin: true,
416
+ ...options
417
+ };
418
+
419
+ if (!this._extensions[PerMessageDeflate.extensionName]) {
420
+ opts.compress = false;
421
+ }
422
+
423
+ this._sender.send(data || EMPTY_BUFFER, opts, cb);
424
+ }
425
+
426
+ /**
427
+ * Forcibly close the connection.
428
+ *
429
+ * @public
430
+ */
431
+ terminate() {
432
+ if (this.readyState === WebSocket.CLOSED) return;
433
+ if (this.readyState === WebSocket.CONNECTING) {
434
+ const msg = 'WebSocket was closed before the connection was established';
435
+ return abortHandshake(this, this._req, msg);
436
+ }
437
+
438
+ if (this._socket) {
439
+ this._readyState = WebSocket.CLOSING;
440
+ this._socket.destroy();
441
+ }
442
+ }
443
+ }
444
+
445
+ /**
446
+ * @constant {Number} CONNECTING
447
+ * @memberof WebSocket
448
+ */
449
+ Object.defineProperty(WebSocket, 'CONNECTING', {
450
+ enumerable: true,
451
+ value: readyStates.indexOf('CONNECTING')
452
+ });
453
+
454
+ /**
455
+ * @constant {Number} CONNECTING
456
+ * @memberof WebSocket.prototype
457
+ */
458
+ Object.defineProperty(WebSocket.prototype, 'CONNECTING', {
459
+ enumerable: true,
460
+ value: readyStates.indexOf('CONNECTING')
461
+ });
462
+
463
+ /**
464
+ * @constant {Number} OPEN
465
+ * @memberof WebSocket
466
+ */
467
+ Object.defineProperty(WebSocket, 'OPEN', {
468
+ enumerable: true,
469
+ value: readyStates.indexOf('OPEN')
470
+ });
471
+
472
+ /**
473
+ * @constant {Number} OPEN
474
+ * @memberof WebSocket.prototype
475
+ */
476
+ Object.defineProperty(WebSocket.prototype, 'OPEN', {
477
+ enumerable: true,
478
+ value: readyStates.indexOf('OPEN')
479
+ });
480
+
481
+ /**
482
+ * @constant {Number} CLOSING
483
+ * @memberof WebSocket
484
+ */
485
+ Object.defineProperty(WebSocket, 'CLOSING', {
486
+ enumerable: true,
487
+ value: readyStates.indexOf('CLOSING')
488
+ });
489
+
490
+ /**
491
+ * @constant {Number} CLOSING
492
+ * @memberof WebSocket.prototype
493
+ */
494
+ Object.defineProperty(WebSocket.prototype, 'CLOSING', {
495
+ enumerable: true,
496
+ value: readyStates.indexOf('CLOSING')
497
+ });
498
+
499
+ /**
500
+ * @constant {Number} CLOSED
501
+ * @memberof WebSocket
502
+ */
503
+ Object.defineProperty(WebSocket, 'CLOSED', {
504
+ enumerable: true,
505
+ value: readyStates.indexOf('CLOSED')
506
+ });
507
+
508
+ /**
509
+ * @constant {Number} CLOSED
510
+ * @memberof WebSocket.prototype
511
+ */
512
+ Object.defineProperty(WebSocket.prototype, 'CLOSED', {
513
+ enumerable: true,
514
+ value: readyStates.indexOf('CLOSED')
515
+ });
516
+
517
+ [
518
+ 'binaryType',
519
+ 'bufferedAmount',
520
+ 'extensions',
521
+ 'protocol',
522
+ 'readyState',
523
+ 'url'
524
+ ].forEach((property) => {
525
+ Object.defineProperty(WebSocket.prototype, property, { enumerable: true });
526
+ });
527
+
528
+ //
529
+ // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
530
+ // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
531
+ //
532
+ ['open', 'error', 'close', 'message'].forEach((method) => {
533
+ Object.defineProperty(WebSocket.prototype, `on${method}`, {
534
+ enumerable: true,
535
+ get() {
536
+ for (const listener of this.listeners(method)) {
537
+ if (listener[kForOnEventAttribute]) return listener[kListener];
538
+ }
539
+
540
+ return null;
541
+ },
542
+ set(handler) {
543
+ for (const listener of this.listeners(method)) {
544
+ if (listener[kForOnEventAttribute]) {
545
+ this.removeListener(method, listener);
546
+ break;
547
+ }
548
+ }
549
+
550
+ if (typeof handler !== 'function') return;
551
+
552
+ this.addEventListener(method, handler, {
553
+ [kForOnEventAttribute]: true
554
+ });
555
+ }
556
+ });
557
+ });
558
+
559
+ WebSocket.prototype.addEventListener = addEventListener;
560
+ WebSocket.prototype.removeEventListener = removeEventListener;
561
+
562
+ module.exports = WebSocket;
563
+
564
+ /**
565
+ * Initialize a WebSocket client.
566
+ *
567
+ * @param {WebSocket} websocket The client to initialize
568
+ * @param {(String|URL)} address The URL to which to connect
569
+ * @param {Array} protocols The subprotocols
570
+ * @param {Object} [options] Connection options
571
+ * @param {Boolean} [options.followRedirects=false] Whether or not to follow
572
+ * redirects
573
+ * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
574
+ * handshake request
575
+ * @param {Number} [options.maxPayload=104857600] The maximum allowed message
576
+ * size
577
+ * @param {Number} [options.maxRedirects=10] The maximum number of redirects
578
+ * allowed
579
+ * @param {String} [options.origin] Value of the `Origin` or
580
+ * `Sec-WebSocket-Origin` header
581
+ * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
582
+ * permessage-deflate
583
+ * @param {Number} [options.protocolVersion=13] Value of the
584
+ * `Sec-WebSocket-Version` header
585
+ * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
586
+ * not to skip UTF-8 validation for text and close messages
587
+ * @private
588
+ */
589
+ function initAsClient(websocket, address, protocols, options) {
590
+ const opts = {
591
+ protocolVersion: protocolVersions[1],
592
+ maxPayload: 100 * 1024 * 1024,
593
+ skipUTF8Validation: false,
594
+ perMessageDeflate: true,
595
+ followRedirects: false,
596
+ maxRedirects: 10,
597
+ ...options,
598
+ createConnection: undefined,
599
+ socketPath: undefined,
600
+ hostname: undefined,
601
+ protocol: undefined,
602
+ timeout: undefined,
603
+ method: undefined,
604
+ host: undefined,
605
+ path: undefined,
606
+ port: undefined
607
+ };
608
+
609
+ if (!protocolVersions.includes(opts.protocolVersion)) {
610
+ throw new RangeError(
611
+ `Unsupported protocol version: ${opts.protocolVersion} ` +
612
+ `(supported versions: ${protocolVersions.join(', ')})`
613
+ );
614
+ }
615
+
616
+ let parsedUrl;
617
+
618
+ if (address instanceof URL) {
619
+ parsedUrl = address;
620
+ websocket._url = address.href;
621
+ } else {
622
+ try {
623
+ parsedUrl = new URL(address);
624
+ } catch (e) {
625
+ throw new SyntaxError(`Invalid URL: ${address}`);
626
+ }
627
+
628
+ websocket._url = address;
629
+ }
630
+
631
+ const isSecure = parsedUrl.protocol === 'wss:';
632
+ const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
633
+
634
+ if (parsedUrl.protocol !== 'ws:' && !isSecure && !isUnixSocket) {
635
+ throw new SyntaxError(
636
+ 'The URL\'s protocol must be one of "ws:", "wss:", or "ws+unix:"'
637
+ );
638
+ }
639
+
640
+ if (isUnixSocket && !parsedUrl.pathname) {
641
+ throw new SyntaxError("The URL's pathname is empty");
642
+ }
643
+
644
+ if (parsedUrl.hash) {
645
+ throw new SyntaxError('The URL contains a fragment identifier');
646
+ }
647
+
648
+ const defaultPort = isSecure ? 443 : 80;
649
+ const key = randomBytes(16).toString('base64');
650
+ const get = isSecure ? https.get : http.get;
651
+ const protocolSet = new Set();
652
+ let perMessageDeflate;
653
+
654
+ opts.createConnection = isSecure ? tlsConnect : netConnect;
655
+ opts.defaultPort = opts.defaultPort || defaultPort;
656
+ opts.port = parsedUrl.port || defaultPort;
657
+ opts.host = parsedUrl.hostname.startsWith('[')
658
+ ? parsedUrl.hostname.slice(1, -1)
659
+ : parsedUrl.hostname;
660
+ opts.headers = {
661
+ 'Sec-WebSocket-Version': opts.protocolVersion,
662
+ 'Sec-WebSocket-Key': key,
663
+ Connection: 'Upgrade',
664
+ Upgrade: 'websocket',
665
+ ...opts.headers
666
+ };
667
+ opts.path = parsedUrl.pathname + parsedUrl.search;
668
+ opts.timeout = opts.handshakeTimeout;
669
+
670
+ if (opts.perMessageDeflate) {
671
+ perMessageDeflate = new PerMessageDeflate(
672
+ opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
673
+ false,
674
+ opts.maxPayload
675
+ );
676
+ opts.headers['Sec-WebSocket-Extensions'] = format({
677
+ [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
678
+ });
679
+ }
680
+ if (protocols.length) {
681
+ for (const protocol of protocols) {
682
+ if (
683
+ typeof protocol !== 'string' ||
684
+ !subprotocolRegex.test(protocol) ||
685
+ protocolSet.has(protocol)
686
+ ) {
687
+ throw new SyntaxError(
688
+ 'An invalid or duplicated subprotocol was specified'
689
+ );
690
+ }
691
+
692
+ protocolSet.add(protocol);
693
+ }
694
+
695
+ opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');
696
+ }
697
+ if (opts.origin) {
698
+ if (opts.protocolVersion < 13) {
699
+ opts.headers['Sec-WebSocket-Origin'] = opts.origin;
700
+ } else {
701
+ opts.headers.Origin = opts.origin;
702
+ }
703
+ }
704
+ if (parsedUrl.username || parsedUrl.password) {
705
+ opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;
706
+ }
707
+
708
+ if (isUnixSocket) {
709
+ const parts = opts.path.split(':');
710
+
711
+ opts.socketPath = parts[0];
712
+ opts.path = parts[1];
713
+ }
714
+
715
+ let req = (websocket._req = get(opts));
716
+
717
+ if (opts.timeout) {
718
+ req.on('timeout', () => {
719
+ abortHandshake(websocket, req, 'Opening handshake has timed out');
720
+ });
721
+ }
722
+
723
+ req.on('error', (err) => {
724
+ if (req === null || req.aborted) return;
725
+
726
+ req = websocket._req = null;
727
+ websocket._readyState = WebSocket.CLOSING;
728
+ websocket.emit('error', err);
729
+ websocket.emitClose();
730
+ });
731
+
732
+ req.on('response', (res) => {
733
+ const location = res.headers.location;
734
+ const statusCode = res.statusCode;
735
+
736
+ if (
737
+ location &&
738
+ opts.followRedirects &&
739
+ statusCode >= 300 &&
740
+ statusCode < 400
741
+ ) {
742
+ if (++websocket._redirects > opts.maxRedirects) {
743
+ abortHandshake(websocket, req, 'Maximum redirects exceeded');
744
+ return;
745
+ }
746
+
747
+ req.abort();
748
+
749
+ const addr = new URL(location, address);
750
+
751
+ initAsClient(websocket, addr, protocols, options);
752
+ } else if (!websocket.emit('unexpected-response', req, res)) {
753
+ abortHandshake(
754
+ websocket,
755
+ req,
756
+ `Unexpected server response: ${res.statusCode}`
757
+ );
758
+ }
759
+ });
760
+
761
+ req.on('upgrade', (res, socket, head) => {
762
+ websocket.emit('upgrade', res);
763
+
764
+ //
765
+ // The user may have closed the connection from a listener of the `upgrade`
766
+ // event.
767
+ //
768
+ if (websocket.readyState !== WebSocket.CONNECTING) return;
769
+
770
+ req = websocket._req = null;
771
+
772
+ const digest = createHash('sha1')
773
+ .update(key + GUID)
774
+ .digest('base64');
775
+
776
+ if (res.headers['sec-websocket-accept'] !== digest) {
777
+ abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
778
+ return;
779
+ }
780
+
781
+ const serverProt = res.headers['sec-websocket-protocol'];
782
+ let protError;
783
+
784
+ if (serverProt !== undefined) {
785
+ if (!protocolSet.size) {
786
+ protError = 'Server sent a subprotocol but none was requested';
787
+ } else if (!protocolSet.has(serverProt)) {
788
+ protError = 'Server sent an invalid subprotocol';
789
+ }
790
+ } else if (protocolSet.size) {
791
+ protError = 'Server sent no subprotocol';
792
+ }
793
+
794
+ if (protError) {
795
+ abortHandshake(websocket, socket, protError);
796
+ return;
797
+ }
798
+
799
+ if (serverProt) websocket._protocol = serverProt;
800
+
801
+ const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
802
+
803
+ if (secWebSocketExtensions !== undefined) {
804
+ if (!perMessageDeflate) {
805
+ const message =
806
+ 'Server sent a Sec-WebSocket-Extensions header but no extension ' +
807
+ 'was requested';
808
+ abortHandshake(websocket, socket, message);
809
+ return;
810
+ }
811
+
812
+ let extensions;
813
+
814
+ try {
815
+ extensions = parse(secWebSocketExtensions);
816
+ } catch (err) {
817
+ const message = 'Invalid Sec-WebSocket-Extensions header';
818
+ abortHandshake(websocket, socket, message);
819
+ return;
820
+ }
821
+
822
+ const extensionNames = Object.keys(extensions);
823
+
824
+ if (
825
+ extensionNames.length !== 1 ||
826
+ extensionNames[0] !== PerMessageDeflate.extensionName
827
+ ) {
828
+ const message = 'Server indicated an extension that was not requested';
829
+ abortHandshake(websocket, socket, message);
830
+ return;
831
+ }
832
+
833
+ try {
834
+ perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
835
+ } catch (err) {
836
+ const message = 'Invalid Sec-WebSocket-Extensions header';
837
+ abortHandshake(websocket, socket, message);
838
+ return;
839
+ }
840
+
841
+ websocket._extensions[PerMessageDeflate.extensionName] =
842
+ perMessageDeflate;
843
+ }
844
+
845
+ websocket.setSocket(socket, head, {
846
+ maxPayload: opts.maxPayload,
847
+ skipUTF8Validation: opts.skipUTF8Validation
848
+ });
849
+ });
850
+ }
851
+
852
+ /**
853
+ * Create a `net.Socket` and initiate a connection.
854
+ *
855
+ * @param {Object} options Connection options
856
+ * @return {net.Socket} The newly created socket used to start the connection
857
+ * @private
858
+ */
859
+ function netConnect(options) {
860
+ options.path = options.socketPath;
861
+ return net.connect(options);
862
+ }
863
+
864
+ /**
865
+ * Create a `tls.TLSSocket` and initiate a connection.
866
+ *
867
+ * @param {Object} options Connection options
868
+ * @return {tls.TLSSocket} The newly created socket used to start the connection
869
+ * @private
870
+ */
871
+ function tlsConnect(options) {
872
+ options.path = undefined;
873
+
874
+ if (!options.servername && options.servername !== '') {
875
+ options.servername = net.isIP(options.host) ? '' : options.host;
876
+ }
877
+
878
+ return tls.connect(options);
879
+ }
880
+
881
+ /**
882
+ * Abort the handshake and emit an error.
883
+ *
884
+ * @param {WebSocket} websocket The WebSocket instance
885
+ * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to
886
+ * abort or the socket to destroy
887
+ * @param {String} message The error message
888
+ * @private
889
+ */
890
+ function abortHandshake(websocket, stream, message) {
891
+ websocket._readyState = WebSocket.CLOSING;
892
+
893
+ const err = new Error(message);
894
+ Error.captureStackTrace(err, abortHandshake);
895
+
896
+ if (stream.setHeader) {
897
+ stream.abort();
898
+
899
+ if (stream.socket && !stream.socket.destroyed) {
900
+ //
901
+ // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
902
+ // called after the request completed. See
903
+ // https://github.com/websockets/ws/issues/1869.
904
+ //
905
+ stream.socket.destroy();
906
+ }
907
+
908
+ stream.once('abort', websocket.emitClose.bind(websocket));
909
+ websocket.emit('error', err);
910
+ } else {
911
+ stream.destroy(err);
912
+ stream.once('error', websocket.emit.bind(websocket, 'error'));
913
+ stream.once('close', websocket.emitClose.bind(websocket));
914
+ }
915
+ }
916
+
917
+ /**
918
+ * Handle cases where the `ping()`, `pong()`, or `send()` methods are called
919
+ * when the `readyState` attribute is `CLOSING` or `CLOSED`.
920
+ *
921
+ * @param {WebSocket} websocket The WebSocket instance
922
+ * @param {*} [data] The data to send
923
+ * @param {Function} [cb] Callback
924
+ * @private
925
+ */
926
+ function sendAfterClose(websocket, data, cb) {
927
+ if (data) {
928
+ const length = toBuffer(data).length;
929
+
930
+ //
931
+ // The `_bufferedAmount` property is used only when the peer is a client and
932
+ // the opening handshake fails. Under these circumstances, in fact, the
933
+ // `setSocket()` method is not called, so the `_socket` and `_sender`
934
+ // properties are set to `null`.
935
+ //
936
+ if (websocket._socket) websocket._sender._bufferedBytes += length;
937
+ else websocket._bufferedAmount += length;
938
+ }
939
+
940
+ if (cb) {
941
+ const err = new Error(
942
+ `WebSocket is not open: readyState ${websocket.readyState} ` +
943
+ `(${readyStates[websocket.readyState]})`
944
+ );
945
+ cb(err);
946
+ }
947
+ }
948
+
949
+ /**
950
+ * The listener of the `Receiver` `'conclude'` event.
951
+ *
952
+ * @param {Number} code The status code
953
+ * @param {Buffer} reason The reason for closing
954
+ * @private
955
+ */
956
+ function receiverOnConclude(code, reason) {
957
+ const websocket = this[kWebSocket];
958
+
959
+ websocket._socket.removeListener('data', socketOnData);
960
+ process.nextTick(resume, websocket._socket);
961
+
962
+ websocket._closeFrameReceived = true;
963
+ websocket._closeMessage = reason;
964
+ websocket._closeCode = code;
965
+
966
+ if (code === 1005) websocket.close();
967
+ else websocket.close(code, reason);
968
+ }
969
+
970
+ /**
971
+ * The listener of the `Receiver` `'drain'` event.
972
+ *
973
+ * @private
974
+ */
975
+ function receiverOnDrain() {
976
+ this[kWebSocket]._socket.resume();
977
+ }
978
+
979
+ /**
980
+ * The listener of the `Receiver` `'error'` event.
981
+ *
982
+ * @param {(RangeError|Error)} err The emitted error
983
+ * @private
984
+ */
985
+ function receiverOnError(err) {
986
+ const websocket = this[kWebSocket];
987
+
988
+ websocket._socket.removeListener('data', socketOnData);
989
+
990
+ //
991
+ // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
992
+ // https://github.com/websockets/ws/issues/1940.
993
+ //
994
+ process.nextTick(resume, websocket._socket);
995
+
996
+ websocket.close(err[kStatusCode]);
997
+ websocket.emit('error', err);
998
+ }
999
+
1000
+ /**
1001
+ * The listener of the `Receiver` `'finish'` event.
1002
+ *
1003
+ * @private
1004
+ */
1005
+ function receiverOnFinish() {
1006
+ this[kWebSocket].emitClose();
1007
+ }
1008
+
1009
+ /**
1010
+ * The listener of the `Receiver` `'message'` event.
1011
+ *
1012
+ * @param {Buffer|ArrayBuffer|Buffer[])} data The message
1013
+ * @param {Boolean} isBinary Specifies whether the message is binary or not
1014
+ * @private
1015
+ */
1016
+ function receiverOnMessage(data, isBinary) {
1017
+ this[kWebSocket].emit('message', data, isBinary);
1018
+ }
1019
+
1020
+ /**
1021
+ * The listener of the `Receiver` `'ping'` event.
1022
+ *
1023
+ * @param {Buffer} data The data included in the ping frame
1024
+ * @private
1025
+ */
1026
+ function receiverOnPing(data) {
1027
+ const websocket = this[kWebSocket];
1028
+
1029
+ websocket.pong(data, !websocket._isServer, NOOP);
1030
+ websocket.emit('ping', data);
1031
+ }
1032
+
1033
+ /**
1034
+ * The listener of the `Receiver` `'pong'` event.
1035
+ *
1036
+ * @param {Buffer} data The data included in the pong frame
1037
+ * @private
1038
+ */
1039
+ function receiverOnPong(data) {
1040
+ this[kWebSocket].emit('pong', data);
1041
+ }
1042
+
1043
+ /**
1044
+ * Resume a readable stream
1045
+ *
1046
+ * @param {Readable} stream The readable stream
1047
+ * @private
1048
+ */
1049
+ function resume(stream) {
1050
+ stream.resume();
1051
+ }
1052
+
1053
+ /**
1054
+ * The listener of the `net.Socket` `'close'` event.
1055
+ *
1056
+ * @private
1057
+ */
1058
+ function socketOnClose() {
1059
+ const websocket = this[kWebSocket];
1060
+
1061
+ this.removeListener('close', socketOnClose);
1062
+ this.removeListener('data', socketOnData);
1063
+ this.removeListener('end', socketOnEnd);
1064
+
1065
+ websocket._readyState = WebSocket.CLOSING;
1066
+
1067
+ let chunk;
1068
+
1069
+ //
1070
+ // The close frame might not have been received or the `'end'` event emitted,
1071
+ // for example, if the socket was destroyed due to an error. Ensure that the
1072
+ // `receiver` stream is closed after writing any remaining buffered data to
1073
+ // it. If the readable side of the socket is in flowing mode then there is no
1074
+ // buffered data as everything has been already written and `readable.read()`
1075
+ // will return `null`. If instead, the socket is paused, any possible buffered
1076
+ // data will be read as a single chunk.
1077
+ //
1078
+ if (
1079
+ !this._readableState.endEmitted &&
1080
+ !websocket._closeFrameReceived &&
1081
+ !websocket._receiver._writableState.errorEmitted &&
1082
+ (chunk = websocket._socket.read()) !== null
1083
+ ) {
1084
+ websocket._receiver.write(chunk);
1085
+ }
1086
+
1087
+ websocket._receiver.end();
1088
+
1089
+ this[kWebSocket] = undefined;
1090
+
1091
+ clearTimeout(websocket._closeTimer);
1092
+
1093
+ if (
1094
+ websocket._receiver._writableState.finished ||
1095
+ websocket._receiver._writableState.errorEmitted
1096
+ ) {
1097
+ websocket.emitClose();
1098
+ } else {
1099
+ websocket._receiver.on('error', receiverOnFinish);
1100
+ websocket._receiver.on('finish', receiverOnFinish);
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * The listener of the `net.Socket` `'data'` event.
1106
+ *
1107
+ * @param {Buffer} chunk A chunk of data
1108
+ * @private
1109
+ */
1110
+ function socketOnData(chunk) {
1111
+ if (!this[kWebSocket]._receiver.write(chunk)) {
1112
+ this.pause();
1113
+ }
1114
+ }
1115
+
1116
+ /**
1117
+ * The listener of the `net.Socket` `'end'` event.
1118
+ *
1119
+ * @private
1120
+ */
1121
+ function socketOnEnd() {
1122
+ const websocket = this[kWebSocket];
1123
+
1124
+ websocket._readyState = WebSocket.CLOSING;
1125
+ websocket._receiver.end();
1126
+ this.end();
1127
+ }
1128
+
1129
+ /**
1130
+ * The listener of the `net.Socket` `'error'` event.
1131
+ *
1132
+ * @private
1133
+ */
1134
+ function socketOnError() {
1135
+ const websocket = this[kWebSocket];
1136
+
1137
+ this.removeListener('error', socketOnError);
1138
+ this.on('error', NOOP);
1139
+
1140
+ if (websocket) {
1141
+ websocket._readyState = WebSocket.CLOSING;
1142
+ this.destroy();
1143
+ }
1144
+ }