source: frontend/node_modules/ws/lib/websocket.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

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