1d7c4468b4df06587e200e6b38fbfaacf6e19f88
[platform/framework/web/wrtjs.git] /
1 'use strict';
2
3 const EventEmitter = require('events');
4 const crypto = require('crypto');
5 const https = require('https');
6 const http = require('http');
7 const net = require('net');
8 const tls = require('tls');
9 const url = require('url');
10
11 const PerMessageDeflate = require('./permessage-deflate');
12 const EventTarget = require('./event-target');
13 const extension = require('./extension');
14 const constants = require('./constants');
15 const Receiver = require('./receiver');
16 const Sender = require('./sender');
17
18 const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
19 const kWebSocket = constants.kWebSocket;
20 const protocolVersions = [8, 13];
21 const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
22
23 /**
24  * Class representing a WebSocket.
25  *
26  * @extends EventEmitter
27  */
28 class WebSocket extends EventEmitter {
29   /**
30    * Create a new `WebSocket`.
31    *
32    * @param {(String|url.Url|url.URL)} address The URL to which to connect
33    * @param {(String|String[])} protocols The subprotocols
34    * @param {Object} options Connection options
35    */
36   constructor(address, protocols, options) {
37     super();
38
39     this.readyState = WebSocket.CONNECTING;
40     this.protocol = '';
41
42     this._binaryType = constants.BINARY_TYPES[0];
43     this._closeFrameReceived = false;
44     this._closeFrameSent = false;
45     this._closeMessage = '';
46     this._closeTimer = null;
47     this._closeCode = 1006;
48     this._extensions = {};
49     this._isServer = true;
50     this._receiver = null;
51     this._sender = null;
52     this._socket = null;
53
54     if (address !== null) {
55       if (Array.isArray(protocols)) {
56         protocols = protocols.join(', ');
57       } else if (typeof protocols === 'object' && protocols !== null) {
58         options = protocols;
59         protocols = undefined;
60       }
61
62       initAsClient.call(this, address, protocols, options);
63     }
64   }
65
66   get CONNECTING() {
67     return WebSocket.CONNECTING;
68   }
69   get CLOSING() {
70     return WebSocket.CLOSING;
71   }
72   get CLOSED() {
73     return WebSocket.CLOSED;
74   }
75   get OPEN() {
76     return WebSocket.OPEN;
77   }
78
79   /**
80    * This deviates from the WHATWG interface since ws doesn't support the required
81    * default "blob" type (instead we define a custom "nodebuffer" type).
82    *
83    * @type {String}
84    */
85   get binaryType() {
86     return this._binaryType;
87   }
88
89   set binaryType(type) {
90     if (!constants.BINARY_TYPES.includes(type)) return;
91
92     this._binaryType = type;
93
94     //
95     // Allow to change `binaryType` on the fly.
96     //
97     if (this._receiver) this._receiver._binaryType = type;
98   }
99
100   /**
101    * @type {Number}
102    */
103   get bufferedAmount() {
104     if (!this._socket) return 0;
105
106     //
107     // `socket.bufferSize` is `undefined` if the socket is closed.
108     //
109     return (this._socket.bufferSize || 0) + this._sender._bufferedBytes;
110   }
111
112   /**
113    * @type {String}
114    */
115   get extensions() {
116     return Object.keys(this._extensions).join();
117   }
118
119   /**
120    * Set up the socket and the internal resources.
121    *
122    * @param {net.Socket} socket The network socket between the server and client
123    * @param {Buffer} head The first packet of the upgraded stream
124    * @param {Number} maxPayload The maximum allowed message size
125    * @private
126    */
127   setSocket(socket, head, maxPayload) {
128     const receiver = new Receiver(
129       this._binaryType,
130       this._extensions,
131       maxPayload
132     );
133
134     this._sender = new Sender(socket, this._extensions);
135     this._receiver = receiver;
136     this._socket = socket;
137
138     receiver[kWebSocket] = this;
139     socket[kWebSocket] = this;
140
141     receiver.on('conclude', receiverOnConclude);
142     receiver.on('drain', receiverOnDrain);
143     receiver.on('error', receiverOnError);
144     receiver.on('message', receiverOnMessage);
145     receiver.on('ping', receiverOnPing);
146     receiver.on('pong', receiverOnPong);
147
148     socket.setTimeout(0);
149     socket.setNoDelay();
150
151     if (head.length > 0) socket.unshift(head);
152
153     socket.on('close', socketOnClose);
154     socket.on('data', socketOnData);
155     socket.on('end', socketOnEnd);
156     socket.on('error', socketOnError);
157
158     this.readyState = WebSocket.OPEN;
159     this.emit('open');
160   }
161
162   /**
163    * Emit the `'close'` event.
164    *
165    * @private
166    */
167   emitClose() {
168     this.readyState = WebSocket.CLOSED;
169
170     if (!this._socket) {
171       this.emit('close', this._closeCode, this._closeMessage);
172       return;
173     }
174
175     if (this._extensions[PerMessageDeflate.extensionName]) {
176       this._extensions[PerMessageDeflate.extensionName].cleanup();
177     }
178
179     this._receiver.removeAllListeners();
180     this.emit('close', this._closeCode, this._closeMessage);
181   }
182
183   /**
184    * Start a closing handshake.
185    *
186    *          +----------+   +-----------+   +----------+
187    *     - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
188    *    |     +----------+   +-----------+   +----------+     |
189    *          +----------+   +-----------+         |
190    * CLOSING  |ws.close()|<--|close frame|<--+-----+       CLOSING
191    *          +----------+   +-----------+   |
192    *    |           |                        |   +---+        |
193    *                +------------------------+-->|fin| - - - -
194    *    |         +---+                      |   +---+
195    *     - - - - -|fin|<---------------------+
196    *              +---+
197    *
198    * @param {Number} code Status code explaining why the connection is closing
199    * @param {String} data A string explaining why the connection is closing
200    * @public
201    */
202   close(code, data) {
203     if (this.readyState === WebSocket.CLOSED) return;
204     if (this.readyState === WebSocket.CONNECTING) {
205       const msg = 'WebSocket was closed before the connection was established';
206       return abortHandshake(this, this._req, msg);
207     }
208
209     if (this.readyState === WebSocket.CLOSING) {
210       if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
211       return;
212     }
213
214     this.readyState = WebSocket.CLOSING;
215     this._sender.close(code, data, !this._isServer, (err) => {
216       //
217       // This error is handled by the `'error'` listener on the socket. We only
218       // want to know if the close frame has been sent here.
219       //
220       if (err) return;
221
222       this._closeFrameSent = true;
223
224       if (this._socket.writable) {
225         if (this._closeFrameReceived) this._socket.end();
226
227         //
228         // Ensure that the connection is closed even if the closing handshake
229         // fails.
230         //
231         this._closeTimer = setTimeout(
232           this._socket.destroy.bind(this._socket),
233           closeTimeout
234         );
235       }
236     });
237   }
238
239   /**
240    * Send a ping.
241    *
242    * @param {*} data The data to send
243    * @param {Boolean} mask Indicates whether or not to mask `data`
244    * @param {Function} cb Callback which is executed when the ping is sent
245    * @public
246    */
247   ping(data, mask, cb) {
248     if (typeof data === 'function') {
249       cb = data;
250       data = mask = undefined;
251     } else if (typeof mask === 'function') {
252       cb = mask;
253       mask = undefined;
254     }
255
256     if (this.readyState !== WebSocket.OPEN) {
257       const err = new Error(
258         `WebSocket is not open: readyState ${this.readyState} ` +
259           `(${readyStates[this.readyState]})`
260       );
261
262       if (cb) return cb(err);
263       throw err;
264     }
265
266     if (typeof data === 'number') data = data.toString();
267     if (mask === undefined) mask = !this._isServer;
268     this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb);
269   }
270
271   /**
272    * Send a pong.
273    *
274    * @param {*} data The data to send
275    * @param {Boolean} mask Indicates whether or not to mask `data`
276    * @param {Function} cb Callback which is executed when the pong is sent
277    * @public
278    */
279   pong(data, mask, cb) {
280     if (typeof data === 'function') {
281       cb = data;
282       data = mask = undefined;
283     } else if (typeof mask === 'function') {
284       cb = mask;
285       mask = undefined;
286     }
287
288     if (this.readyState !== WebSocket.OPEN) {
289       const err = new Error(
290         `WebSocket is not open: readyState ${this.readyState} ` +
291           `(${readyStates[this.readyState]})`
292       );
293
294       if (cb) return cb(err);
295       throw err;
296     }
297
298     if (typeof data === 'number') data = data.toString();
299     if (mask === undefined) mask = !this._isServer;
300     this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb);
301   }
302
303   /**
304    * Send a data message.
305    *
306    * @param {*} data The message to send
307    * @param {Object} options Options object
308    * @param {Boolean} options.compress Specifies whether or not to compress `data`
309    * @param {Boolean} options.binary Specifies whether `data` is binary or text
310    * @param {Boolean} options.fin Specifies whether the fragment is the last one
311    * @param {Boolean} options.mask Specifies whether or not to mask `data`
312    * @param {Function} cb Callback which is executed when data is written out
313    * @public
314    */
315   send(data, options, cb) {
316     if (typeof options === 'function') {
317       cb = options;
318       options = {};
319     }
320
321     if (this.readyState !== WebSocket.OPEN) {
322       const err = new Error(
323         `WebSocket is not open: readyState ${this.readyState} ` +
324           `(${readyStates[this.readyState]})`
325       );
326
327       if (cb) return cb(err);
328       throw err;
329     }
330
331     if (typeof data === 'number') data = data.toString();
332
333     const opts = Object.assign(
334       {
335         binary: typeof data !== 'string',
336         mask: !this._isServer,
337         compress: true,
338         fin: true
339       },
340       options
341     );
342
343     if (!this._extensions[PerMessageDeflate.extensionName]) {
344       opts.compress = false;
345     }
346
347     this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
348   }
349
350   /**
351    * Forcibly close the connection.
352    *
353    * @public
354    */
355   terminate() {
356     if (this.readyState === WebSocket.CLOSED) return;
357     if (this.readyState === WebSocket.CONNECTING) {
358       const msg = 'WebSocket was closed before the connection was established';
359       return abortHandshake(this, this._req, msg);
360     }
361
362     if (this._socket) {
363       this.readyState = WebSocket.CLOSING;
364       this._socket.destroy();
365     }
366   }
367 }
368
369 readyStates.forEach((readyState, i) => {
370   WebSocket[readyState] = i;
371 });
372
373 //
374 // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
375 // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
376 //
377 ['open', 'error', 'close', 'message'].forEach((method) => {
378   Object.defineProperty(WebSocket.prototype, `on${method}`, {
379     /**
380      * Return the listener of the event.
381      *
382      * @return {(Function|undefined)} The event listener or `undefined`
383      * @public
384      */
385     get() {
386       const listeners = this.listeners(method);
387       for (var i = 0; i < listeners.length; i++) {
388         if (listeners[i]._listener) return listeners[i]._listener;
389       }
390
391       return undefined;
392     },
393     /**
394      * Add a listener for the event.
395      *
396      * @param {Function} listener The listener to add
397      * @public
398      */
399     set(listener) {
400       const listeners = this.listeners(method);
401       for (var i = 0; i < listeners.length; i++) {
402         //
403         // Remove only the listeners added via `addEventListener`.
404         //
405         if (listeners[i]._listener) this.removeListener(method, listeners[i]);
406       }
407       this.addEventListener(method, listener);
408     }
409   });
410 });
411
412 WebSocket.prototype.addEventListener = EventTarget.addEventListener;
413 WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
414
415 module.exports = WebSocket;
416
417 /**
418  * Initialize a WebSocket client.
419  *
420  * @param {(String|url.Url|url.URL)} address The URL to which to connect
421  * @param {String} protocols The subprotocols
422  * @param {Object} options Connection options
423  * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
424  * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request
425  * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
426  * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header
427  * @param {Number} options.maxPayload The maximum allowed message size
428  * @private
429  */
430 function initAsClient(address, protocols, options) {
431   options = Object.assign(
432     {
433       protocolVersion: protocolVersions[1],
434       perMessageDeflate: true,
435       maxPayload: 100 * 1024 * 1024
436     },
437     options,
438     {
439       createConnection: undefined,
440       socketPath: undefined,
441       hostname: undefined,
442       protocol: undefined,
443       timeout: undefined,
444       method: undefined,
445       auth: undefined,
446       host: undefined,
447       path: undefined,
448       port: undefined
449     }
450   );
451
452   if (!protocolVersions.includes(options.protocolVersion)) {
453     throw new RangeError(
454       `Unsupported protocol version: ${options.protocolVersion} ` +
455         `(supported versions: ${protocolVersions.join(', ')})`
456     );
457   }
458
459   this._isServer = false;
460
461   var parsedUrl;
462
463   if (typeof address === 'object' && address.href !== undefined) {
464     parsedUrl = address;
465     this.url = address.href;
466   } else {
467     //
468     // The WHATWG URL constructor is not available on Node.js < 6.13.0
469     //
470     parsedUrl = url.URL ? new url.URL(address) : url.parse(address);
471     this.url = address;
472   }
473
474   const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
475
476   if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
477     throw new Error(`Invalid URL: ${this.url}`);
478   }
479
480   const isSecure =
481     parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
482   const defaultPort = isSecure ? 443 : 80;
483   const key = crypto.randomBytes(16).toString('base64');
484   const httpObj = isSecure ? https : http;
485   const path = parsedUrl.search
486     ? `${parsedUrl.pathname || '/'}${parsedUrl.search}`
487     : parsedUrl.pathname || '/';
488   var perMessageDeflate;
489
490   options.createConnection = isSecure ? tlsConnect : netConnect;
491   options.defaultPort = options.defaultPort || defaultPort;
492   options.port = parsedUrl.port || defaultPort;
493   options.host = parsedUrl.hostname.startsWith('[')
494     ? parsedUrl.hostname.slice(1, -1)
495     : parsedUrl.hostname;
496   options.headers = Object.assign(
497     {
498       'Sec-WebSocket-Version': options.protocolVersion,
499       'Sec-WebSocket-Key': key,
500       Connection: 'Upgrade',
501       Upgrade: 'websocket'
502     },
503     options.headers
504   );
505   options.path = path;
506   options.timeout = options.handshakeTimeout;
507
508   if (options.perMessageDeflate) {
509     perMessageDeflate = new PerMessageDeflate(
510       options.perMessageDeflate !== true ? options.perMessageDeflate : {},
511       false,
512       options.maxPayload
513     );
514     options.headers['Sec-WebSocket-Extensions'] = extension.format({
515       [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
516     });
517   }
518   if (protocols) {
519     options.headers['Sec-WebSocket-Protocol'] = protocols;
520   }
521   if (options.origin) {
522     if (options.protocolVersion < 13) {
523       options.headers['Sec-WebSocket-Origin'] = options.origin;
524     } else {
525       options.headers.Origin = options.origin;
526     }
527   }
528   if (parsedUrl.auth) {
529     options.auth = parsedUrl.auth;
530   } else if (parsedUrl.username || parsedUrl.password) {
531     options.auth = `${parsedUrl.username}:${parsedUrl.password}`;
532   }
533
534   if (isUnixSocket) {
535     const parts = path.split(':');
536
537     options.socketPath = parts[0];
538     options.path = parts[1];
539   }
540
541   var req = (this._req = httpObj.get(options));
542
543   if (options.handshakeTimeout) {
544     req.on('timeout', () => {
545       abortHandshake(this, req, 'Opening handshake has timed out');
546     });
547   }
548
549   req.on('error', (err) => {
550     if (this._req.aborted) return;
551
552     req = this._req = null;
553     this.readyState = WebSocket.CLOSING;
554     this.emit('error', err);
555     this.emitClose();
556   });
557
558   req.on('response', (res) => {
559     if (this.emit('unexpected-response', req, res)) return;
560
561     abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`);
562   });
563
564   req.on('upgrade', (res, socket, head) => {
565     this.emit('upgrade', res);
566
567     //
568     // The user may have closed the connection from a listener of the `upgrade`
569     // event.
570     //
571     if (this.readyState !== WebSocket.CONNECTING) return;
572
573     req = this._req = null;
574
575     const digest = crypto
576       .createHash('sha1')
577       .update(key + constants.GUID, 'binary')
578       .digest('base64');
579
580     if (res.headers['sec-websocket-accept'] !== digest) {
581       abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header');
582       return;
583     }
584
585     const serverProt = res.headers['sec-websocket-protocol'];
586     const protList = (protocols || '').split(/, */);
587     var protError;
588
589     if (!protocols && serverProt) {
590       protError = 'Server sent a subprotocol but none was requested';
591     } else if (protocols && !serverProt) {
592       protError = 'Server sent no subprotocol';
593     } else if (serverProt && !protList.includes(serverProt)) {
594       protError = 'Server sent an invalid subprotocol';
595     }
596
597     if (protError) {
598       abortHandshake(this, socket, protError);
599       return;
600     }
601
602     if (serverProt) this.protocol = serverProt;
603
604     if (perMessageDeflate) {
605       try {
606         const extensions = extension.parse(
607           res.headers['sec-websocket-extensions']
608         );
609
610         if (extensions[PerMessageDeflate.extensionName]) {
611           perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
612           this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
613         }
614       } catch (err) {
615         abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header');
616         return;
617       }
618     }
619
620     this.setSocket(socket, head, options.maxPayload);
621   });
622 }
623
624 /**
625  * Create a `net.Socket` and initiate a connection.
626  *
627  * @param {Object} options Connection options
628  * @return {net.Socket} The newly created socket used to start the connection
629  * @private
630  */
631 function netConnect(options) {
632   //
633   // Override `options.path` only if `options` is a copy of the original options
634   // object. This is always true on Node.js >= 8 but not on Node.js 6 where
635   // `options.socketPath` might be `undefined` even if the `socketPath` option
636   // was originally set.
637   //
638   if (options.protocolVersion) options.path = options.socketPath;
639   return net.connect(options);
640 }
641
642 /**
643  * Create a `tls.TLSSocket` and initiate a connection.
644  *
645  * @param {Object} options Connection options
646  * @return {tls.TLSSocket} The newly created socket used to start the connection
647  * @private
648  */
649 function tlsConnect(options) {
650   options.path = undefined;
651   options.servername = options.servername || options.host;
652   return tls.connect(options);
653 }
654
655 /**
656  * Abort the handshake and emit an error.
657  *
658  * @param {WebSocket} websocket The WebSocket instance
659  * @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
660  *     socket to destroy
661  * @param {String} message The error message
662  * @private
663  */
664 function abortHandshake(websocket, stream, message) {
665   websocket.readyState = WebSocket.CLOSING;
666
667   const err = new Error(message);
668   Error.captureStackTrace(err, abortHandshake);
669
670   if (stream.setHeader) {
671     stream.abort();
672     stream.once('abort', websocket.emitClose.bind(websocket));
673     websocket.emit('error', err);
674   } else {
675     stream.destroy(err);
676     stream.once('error', websocket.emit.bind(websocket, 'error'));
677     stream.once('close', websocket.emitClose.bind(websocket));
678   }
679 }
680
681 /**
682  * The listener of the `Receiver` `'conclude'` event.
683  *
684  * @param {Number} code The status code
685  * @param {String} reason The reason for closing
686  * @private
687  */
688 function receiverOnConclude(code, reason) {
689   const websocket = this[kWebSocket];
690
691   websocket._socket.removeListener('data', socketOnData);
692   websocket._socket.resume();
693
694   websocket._closeFrameReceived = true;
695   websocket._closeMessage = reason;
696   websocket._closeCode = code;
697
698   if (code === 1005) websocket.close();
699   else websocket.close(code, reason);
700 }
701
702 /**
703  * The listener of the `Receiver` `'drain'` event.
704  *
705  * @private
706  */
707 function receiverOnDrain() {
708   this[kWebSocket]._socket.resume();
709 }
710
711 /**
712  * The listener of the `Receiver` `'error'` event.
713  *
714  * @param {(RangeError|Error)} err The emitted error
715  * @private
716  */
717 function receiverOnError(err) {
718   const websocket = this[kWebSocket];
719
720   websocket._socket.removeListener('data', socketOnData);
721
722   websocket.readyState = WebSocket.CLOSING;
723   websocket._closeCode = err[constants.kStatusCode];
724   websocket.emit('error', err);
725   websocket._socket.destroy();
726 }
727
728 /**
729  * The listener of the `Receiver` `'finish'` event.
730  *
731  * @private
732  */
733 function receiverOnFinish() {
734   this[kWebSocket].emitClose();
735 }
736
737 /**
738  * The listener of the `Receiver` `'message'` event.
739  *
740  * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
741  * @private
742  */
743 function receiverOnMessage(data) {
744   this[kWebSocket].emit('message', data);
745 }
746
747 /**
748  * The listener of the `Receiver` `'ping'` event.
749  *
750  * @param {Buffer} data The data included in the ping frame
751  * @private
752  */
753 function receiverOnPing(data) {
754   const websocket = this[kWebSocket];
755
756   websocket.pong(data, !websocket._isServer, constants.NOOP);
757   websocket.emit('ping', data);
758 }
759
760 /**
761  * The listener of the `Receiver` `'pong'` event.
762  *
763  * @param {Buffer} data The data included in the pong frame
764  * @private
765  */
766 function receiverOnPong(data) {
767   this[kWebSocket].emit('pong', data);
768 }
769
770 /**
771  * The listener of the `net.Socket` `'close'` event.
772  *
773  * @private
774  */
775 function socketOnClose() {
776   const websocket = this[kWebSocket];
777
778   this.removeListener('close', socketOnClose);
779   this.removeListener('end', socketOnEnd);
780
781   websocket.readyState = WebSocket.CLOSING;
782
783   //
784   // The close frame might not have been received or the `'end'` event emitted,
785   // for example, if the socket was destroyed due to an error. Ensure that the
786   // `receiver` stream is closed after writing any remaining buffered data to
787   // it. If the readable side of the socket is in flowing mode then there is no
788   // buffered data as everything has been already written and `readable.read()`
789   // will return `null`. If instead, the socket is paused, any possible buffered
790   // data will be read as a single chunk and emitted synchronously in a single
791   // `'data'` event.
792   //
793   websocket._socket.read();
794   websocket._receiver.end();
795
796   this.removeListener('data', socketOnData);
797   this[kWebSocket] = undefined;
798
799   clearTimeout(websocket._closeTimer);
800
801   if (
802     websocket._receiver._writableState.finished ||
803     websocket._receiver._writableState.errorEmitted
804   ) {
805     websocket.emitClose();
806   } else {
807     websocket._receiver.on('error', receiverOnFinish);
808     websocket._receiver.on('finish', receiverOnFinish);
809   }
810 }
811
812 /**
813  * The listener of the `net.Socket` `'data'` event.
814  *
815  * @param {Buffer} chunk A chunk of data
816  * @private
817  */
818 function socketOnData(chunk) {
819   if (!this[kWebSocket]._receiver.write(chunk)) {
820     this.pause();
821   }
822 }
823
824 /**
825  * The listener of the `net.Socket` `'end'` event.
826  *
827  * @private
828  */
829 function socketOnEnd() {
830   const websocket = this[kWebSocket];
831
832   websocket.readyState = WebSocket.CLOSING;
833   websocket._receiver.end();
834   this.end();
835 }
836
837 /**
838  * The listener of the `net.Socket` `'error'` event.
839  *
840  * @private
841  */
842 function socketOnError() {
843   const websocket = this[kWebSocket];
844
845   this.removeListener('error', socketOnError);
846   this.on('error', constants.NOOP);
847
848   if (websocket) {
849     websocket.readyState = WebSocket.CLOSING;
850     this.destroy();
851   }
852 }