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;
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | class WebSocket extends EventEmitter {
|
29 | |
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
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 () { return WebSocket.CONNECTING; }
|
67 | get CLOSING () { return WebSocket.CLOSING; }
|
68 | get CLOSED () { return WebSocket.CLOSED; }
|
69 | get OPEN () { return WebSocket.OPEN; }
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | get binaryType () {
|
78 | return this._binaryType;
|
79 | }
|
80 |
|
81 | set binaryType (type) {
|
82 | if (constants.BINARY_TYPES.indexOf(type) < 0) return;
|
83 |
|
84 | this._binaryType = type;
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | if (this._receiver) this._receiver._binaryType = type;
|
90 | }
|
91 |
|
92 | |
93 |
|
94 |
|
95 | get bufferedAmount () {
|
96 | if (!this._socket) return 0;
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | return (this._socket.bufferSize || 0) + this._sender._bufferedBytes;
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 | get extensions () {
|
108 | return Object.keys(this._extensions).join();
|
109 | }
|
110 |
|
111 | |
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | setSocket (socket, head, maxPayload) {
|
120 | const receiver = new Receiver(
|
121 | this._binaryType,
|
122 | this._extensions,
|
123 | maxPayload
|
124 | );
|
125 |
|
126 | this._sender = new Sender(socket, this._extensions);
|
127 | this._receiver = receiver;
|
128 | this._socket = socket;
|
129 |
|
130 | receiver[kWebSocket] = this;
|
131 | socket[kWebSocket] = this;
|
132 |
|
133 | receiver.on('conclude', receiverOnConclude);
|
134 | receiver.on('drain', receiverOnDrain);
|
135 | receiver.on('error', receiverOnError);
|
136 | receiver.on('message', receiverOnMessage);
|
137 | receiver.on('ping', receiverOnPing);
|
138 | receiver.on('pong', receiverOnPong);
|
139 |
|
140 | socket.setTimeout(0);
|
141 | socket.setNoDelay();
|
142 |
|
143 | if (head.length > 0) socket.unshift(head);
|
144 |
|
145 | socket.on('close', socketOnClose);
|
146 | socket.on('data', socketOnData);
|
147 | socket.on('end', socketOnEnd);
|
148 | socket.on('error', socketOnError);
|
149 |
|
150 | this.readyState = WebSocket.OPEN;
|
151 | this.emit('open');
|
152 | }
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 | emitClose () {
|
160 | this.readyState = WebSocket.CLOSED;
|
161 |
|
162 | if (!this._socket) {
|
163 | this.emit('close', this._closeCode, this._closeMessage);
|
164 | return;
|
165 | }
|
166 |
|
167 | if (this._extensions[PerMessageDeflate.extensionName]) {
|
168 | this._extensions[PerMessageDeflate.extensionName].cleanup();
|
169 | }
|
170 |
|
171 | this._receiver.removeAllListeners();
|
172 | this.emit('close', this._closeCode, this._closeMessage);
|
173 | }
|
174 |
|
175 | |
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 | close (code, data) {
|
195 | if (this.readyState === WebSocket.CLOSED) return;
|
196 | if (this.readyState === WebSocket.CONNECTING) {
|
197 | const msg = 'WebSocket was closed before the connection was established';
|
198 | return abortHandshake(this, this._req, msg);
|
199 | }
|
200 |
|
201 | if (this.readyState === WebSocket.CLOSING) {
|
202 | if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
|
203 | return;
|
204 | }
|
205 |
|
206 | this.readyState = WebSocket.CLOSING;
|
207 | this._sender.close(code, data, !this._isServer, (err) => {
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | if (err) return;
|
213 |
|
214 | this._closeFrameSent = true;
|
215 |
|
216 | if (this._socket.writable) {
|
217 | if (this._closeFrameReceived) this._socket.end();
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | this._closeTimer = setTimeout(
|
224 | this._socket.destroy.bind(this._socket),
|
225 | closeTimeout
|
226 | );
|
227 | }
|
228 | });
|
229 | }
|
230 |
|
231 | |
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 | ping (data, mask, cb) {
|
240 | if (typeof data === 'function') {
|
241 | cb = data;
|
242 | data = mask = undefined;
|
243 | } else if (typeof mask === 'function') {
|
244 | cb = mask;
|
245 | mask = undefined;
|
246 | }
|
247 |
|
248 | if (this.readyState !== WebSocket.OPEN) {
|
249 | const err = new Error(
|
250 | `WebSocket is not open: readyState ${this.readyState} ` +
|
251 | `(${readyStates[this.readyState]})`
|
252 | );
|
253 |
|
254 | if (cb) return cb(err);
|
255 | throw err;
|
256 | }
|
257 |
|
258 | if (typeof data === 'number') data = data.toString();
|
259 | if (mask === undefined) mask = !this._isServer;
|
260 | this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb);
|
261 | }
|
262 |
|
263 | |
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | pong (data, mask, cb) {
|
272 | if (typeof data === 'function') {
|
273 | cb = data;
|
274 | data = mask = undefined;
|
275 | } else if (typeof mask === 'function') {
|
276 | cb = mask;
|
277 | mask = undefined;
|
278 | }
|
279 |
|
280 | if (this.readyState !== WebSocket.OPEN) {
|
281 | const err = new Error(
|
282 | `WebSocket is not open: readyState ${this.readyState} ` +
|
283 | `(${readyStates[this.readyState]})`
|
284 | );
|
285 |
|
286 | if (cb) return cb(err);
|
287 | throw err;
|
288 | }
|
289 |
|
290 | if (typeof data === 'number') data = data.toString();
|
291 | if (mask === undefined) mask = !this._isServer;
|
292 | this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb);
|
293 | }
|
294 |
|
295 | |
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | send (data, options, cb) {
|
308 | if (typeof options === 'function') {
|
309 | cb = options;
|
310 | options = {};
|
311 | }
|
312 |
|
313 | if (this.readyState !== WebSocket.OPEN) {
|
314 | const err = new Error(
|
315 | `WebSocket is not open: readyState ${this.readyState} ` +
|
316 | `(${readyStates[this.readyState]})`
|
317 | );
|
318 |
|
319 | if (cb) return cb(err);
|
320 | throw err;
|
321 | }
|
322 |
|
323 | if (typeof data === 'number') data = data.toString();
|
324 |
|
325 | const opts = Object.assign({
|
326 | binary: typeof data !== 'string',
|
327 | mask: !this._isServer,
|
328 | compress: true,
|
329 | fin: true
|
330 | }, options);
|
331 |
|
332 | if (!this._extensions[PerMessageDeflate.extensionName]) {
|
333 | opts.compress = false;
|
334 | }
|
335 |
|
336 | this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
|
337 | }
|
338 |
|
339 | |
340 |
|
341 |
|
342 |
|
343 |
|
344 | terminate () {
|
345 | if (this.readyState === WebSocket.CLOSED) return;
|
346 | if (this.readyState === WebSocket.CONNECTING) {
|
347 | const msg = 'WebSocket was closed before the connection was established';
|
348 | return abortHandshake(this, this._req, msg);
|
349 | }
|
350 |
|
351 | if (this._socket) {
|
352 | this.readyState = WebSocket.CLOSING;
|
353 | this._socket.destroy();
|
354 | }
|
355 | }
|
356 | }
|
357 |
|
358 | readyStates.forEach((readyState, i) => {
|
359 | WebSocket[readyStates[i]] = i;
|
360 | });
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 | ['open', 'error', 'close', 'message'].forEach((method) => {
|
367 | Object.defineProperty(WebSocket.prototype, `on${method}`, {
|
368 | |
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 | get () {
|
375 | const listeners = this.listeners(method);
|
376 | for (var i = 0; i < listeners.length; i++) {
|
377 | if (listeners[i]._listener) return listeners[i]._listener;
|
378 | }
|
379 | },
|
380 | |
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 | set (listener) {
|
387 | const listeners = this.listeners(method);
|
388 | for (var i = 0; i < listeners.length; i++) {
|
389 |
|
390 |
|
391 |
|
392 | if (listeners[i]._listener) this.removeListener(method, listeners[i]);
|
393 | }
|
394 | this.addEventListener(method, listener);
|
395 | }
|
396 | });
|
397 | });
|
398 |
|
399 | WebSocket.prototype.addEventListener = EventTarget.addEventListener;
|
400 | WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
|
401 |
|
402 | module.exports = WebSocket;
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 | function initAsClient (address, protocols, options) {
|
418 | options = Object.assign({
|
419 | protocolVersion: protocolVersions[1],
|
420 | perMessageDeflate: true,
|
421 | maxPayload: 100 * 1024 * 1024
|
422 | }, options, {
|
423 | createConnection: undefined,
|
424 | socketPath: undefined,
|
425 | hostname: undefined,
|
426 | protocol: undefined,
|
427 | timeout: undefined,
|
428 | method: undefined,
|
429 | auth: undefined,
|
430 | host: undefined,
|
431 | path: undefined,
|
432 | port: undefined
|
433 | });
|
434 |
|
435 | if (protocolVersions.indexOf(options.protocolVersion) === -1) {
|
436 | throw new RangeError(
|
437 | `Unsupported protocol version: ${options.protocolVersion} ` +
|
438 | `(supported versions: ${protocolVersions.join(', ')})`
|
439 | );
|
440 | }
|
441 |
|
442 | this._isServer = false;
|
443 |
|
444 | var parsedUrl;
|
445 |
|
446 | if (typeof address === 'object' && address.href !== undefined) {
|
447 | parsedUrl = address;
|
448 | this.url = address.href;
|
449 | } else {
|
450 | parsedUrl = new URL(address);
|
451 | this.url = address;
|
452 | }
|
453 |
|
454 | const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
|
455 |
|
456 | if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
|
457 | throw new Error(`Invalid URL: ${this.url}`);
|
458 | }
|
459 |
|
460 | const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
|
461 | const key = crypto.randomBytes(16).toString('base64');
|
462 | const httpObj = isSecure ? https : http;
|
463 | const path = parsedUrl.search
|
464 | ? `${parsedUrl.pathname || '/'}${parsedUrl.search}`
|
465 | : parsedUrl.pathname || '/';
|
466 | var perMessageDeflate;
|
467 |
|
468 | options.createConnection = isSecure ? tlsConnect : netConnect;
|
469 | options.port = parsedUrl.port || (isSecure ? 443 : 80);
|
470 | options.host = parsedUrl.hostname.startsWith('[')
|
471 | ? parsedUrl.hostname.slice(1, -1)
|
472 | : parsedUrl.hostname;
|
473 | options.headers = Object.assign({
|
474 | 'Sec-WebSocket-Version': options.protocolVersion,
|
475 | 'Sec-WebSocket-Key': key,
|
476 | 'Connection': 'Upgrade',
|
477 | 'Upgrade': 'websocket'
|
478 | }, options.headers);
|
479 | options.path = path;
|
480 |
|
481 | if (options.perMessageDeflate) {
|
482 | perMessageDeflate = new PerMessageDeflate(
|
483 | options.perMessageDeflate !== true ? options.perMessageDeflate : {},
|
484 | false,
|
485 | options.maxPayload
|
486 | );
|
487 | options.headers['Sec-WebSocket-Extensions'] = extension.format({
|
488 | [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
|
489 | });
|
490 | }
|
491 | if (protocols) {
|
492 | options.headers['Sec-WebSocket-Protocol'] = protocols;
|
493 | }
|
494 | if (options.origin) {
|
495 | if (options.protocolVersion < 13) {
|
496 | options.headers['Sec-WebSocket-Origin'] = options.origin;
|
497 | } else {
|
498 | options.headers.Origin = options.origin;
|
499 | }
|
500 | }
|
501 | if (parsedUrl.auth) {
|
502 | options.auth = parsedUrl.auth;
|
503 | } else if (parsedUrl.username || parsedUrl.password) {
|
504 | options.auth = `${parsedUrl.username}:${parsedUrl.password}`;
|
505 | }
|
506 |
|
507 | if (isUnixSocket) {
|
508 | const parts = path.split(':');
|
509 |
|
510 | options.socketPath = parts[0];
|
511 | options.path = parts[1];
|
512 | }
|
513 |
|
514 | var req = this._req = httpObj.get(options);
|
515 |
|
516 | if (options.handshakeTimeout) {
|
517 | req.setTimeout(
|
518 | options.handshakeTimeout,
|
519 | () => abortHandshake(this, req, 'Opening handshake has timed out')
|
520 | );
|
521 | }
|
522 |
|
523 | req.on('error', (err) => {
|
524 | if (this._req.aborted) return;
|
525 |
|
526 | req = this._req = null;
|
527 | this.readyState = WebSocket.CLOSING;
|
528 | this.emit('error', err);
|
529 | this.emitClose();
|
530 | });
|
531 |
|
532 | req.on('response', (res) => {
|
533 | if (this.emit('unexpected-response', req, res)) return;
|
534 |
|
535 | abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`);
|
536 | });
|
537 |
|
538 | req.on('upgrade', (res, socket, head) => {
|
539 | this.emit('upgrade', res);
|
540 |
|
541 |
|
542 |
|
543 |
|
544 |
|
545 | if (this.readyState !== WebSocket.CONNECTING) return;
|
546 |
|
547 | req = this._req = null;
|
548 |
|
549 | const digest = crypto.createHash('sha1')
|
550 | .update(key + constants.GUID, 'binary')
|
551 | .digest('base64');
|
552 |
|
553 | if (res.headers['sec-websocket-accept'] !== digest) {
|
554 | abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header');
|
555 | return;
|
556 | }
|
557 |
|
558 | const serverProt = res.headers['sec-websocket-protocol'];
|
559 | const protList = (protocols || '').split(/, */);
|
560 | var protError;
|
561 |
|
562 | if (!protocols && serverProt) {
|
563 | protError = 'Server sent a subprotocol but none was requested';
|
564 | } else if (protocols && !serverProt) {
|
565 | protError = 'Server sent no subprotocol';
|
566 | } else if (serverProt && protList.indexOf(serverProt) === -1) {
|
567 | protError = 'Server sent an invalid subprotocol';
|
568 | }
|
569 |
|
570 | if (protError) {
|
571 | abortHandshake(this, socket, protError);
|
572 | return;
|
573 | }
|
574 |
|
575 | if (serverProt) this.protocol = serverProt;
|
576 |
|
577 | if (perMessageDeflate) {
|
578 | try {
|
579 | const extensions = extension.parse(
|
580 | res.headers['sec-websocket-extensions']
|
581 | );
|
582 |
|
583 | if (extensions[PerMessageDeflate.extensionName]) {
|
584 | perMessageDeflate.accept(
|
585 | extensions[PerMessageDeflate.extensionName]
|
586 | );
|
587 | this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
588 | }
|
589 | } catch (err) {
|
590 | abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header');
|
591 | return;
|
592 | }
|
593 | }
|
594 |
|
595 | this.setSocket(socket, head, options.maxPayload);
|
596 | });
|
597 | }
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 | function netConnect (options) {
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 | if (options.protocolVersion) options.path = options.socketPath;
|
614 | return net.connect(options);
|
615 | }
|
616 |
|
617 |
|
618 |
|
619 |
|
620 |
|
621 |
|
622 |
|
623 |
|
624 | function tlsConnect (options) {
|
625 | options.path = undefined;
|
626 | options.servername = options.servername || options.host;
|
627 | return tls.connect(options);
|
628 | }
|
629 |
|
630 |
|
631 |
|
632 |
|
633 |
|
634 |
|
635 |
|
636 |
|
637 |
|
638 |
|
639 | function abortHandshake (websocket, stream, message) {
|
640 | websocket.readyState = WebSocket.CLOSING;
|
641 |
|
642 | const err = new Error(message);
|
643 | Error.captureStackTrace(err, abortHandshake);
|
644 |
|
645 | if (stream.setHeader) {
|
646 | stream.abort();
|
647 | stream.once('abort', websocket.emitClose.bind(websocket));
|
648 | websocket.emit('error', err);
|
649 | } else {
|
650 | stream.destroy(err);
|
651 | stream.once('error', websocket.emit.bind(websocket, 'error'));
|
652 | stream.once('close', websocket.emitClose.bind(websocket));
|
653 | }
|
654 | }
|
655 |
|
656 |
|
657 |
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 | function receiverOnConclude (code, reason) {
|
664 | const websocket = this[kWebSocket];
|
665 |
|
666 | websocket._socket.removeListener('data', socketOnData);
|
667 | websocket._socket.resume();
|
668 |
|
669 | websocket._closeFrameReceived = true;
|
670 | websocket._closeMessage = reason;
|
671 | websocket._closeCode = code;
|
672 |
|
673 | if (code === 1005) websocket.close();
|
674 | else websocket.close(code, reason);
|
675 | }
|
676 |
|
677 |
|
678 |
|
679 |
|
680 |
|
681 |
|
682 | function receiverOnDrain () {
|
683 | this[kWebSocket]._socket.resume();
|
684 | }
|
685 |
|
686 |
|
687 |
|
688 |
|
689 |
|
690 |
|
691 |
|
692 | function receiverOnError (err) {
|
693 | const websocket = this[kWebSocket];
|
694 |
|
695 | websocket._socket.removeListener('data', socketOnData);
|
696 |
|
697 | websocket.readyState = WebSocket.CLOSING;
|
698 | websocket._closeCode = err[constants.kStatusCode];
|
699 | websocket.emit('error', err);
|
700 | websocket._socket.destroy();
|
701 | }
|
702 |
|
703 |
|
704 |
|
705 |
|
706 |
|
707 |
|
708 | function receiverOnFinish () {
|
709 | this[kWebSocket].emitClose();
|
710 | }
|
711 |
|
712 |
|
713 |
|
714 |
|
715 |
|
716 |
|
717 |
|
718 | function receiverOnMessage (data) {
|
719 | this[kWebSocket].emit('message', data);
|
720 | }
|
721 |
|
722 |
|
723 |
|
724 |
|
725 |
|
726 |
|
727 |
|
728 | function receiverOnPing (data) {
|
729 | const websocket = this[kWebSocket];
|
730 |
|
731 | websocket.pong(data, !websocket._isServer, constants.NOOP);
|
732 | websocket.emit('ping', data);
|
733 | }
|
734 |
|
735 |
|
736 |
|
737 |
|
738 |
|
739 |
|
740 |
|
741 | function receiverOnPong (data) {
|
742 | this[kWebSocket].emit('pong', data);
|
743 | }
|
744 |
|
745 |
|
746 |
|
747 |
|
748 |
|
749 |
|
750 | function socketOnClose () {
|
751 | const websocket = this[kWebSocket];
|
752 |
|
753 | this.removeListener('close', socketOnClose);
|
754 | this.removeListener('end', socketOnEnd);
|
755 |
|
756 | websocket.readyState = WebSocket.CLOSING;
|
757 |
|
758 |
|
759 |
|
760 |
|
761 |
|
762 |
|
763 |
|
764 |
|
765 |
|
766 |
|
767 |
|
768 | websocket._socket.read();
|
769 | websocket._receiver.end();
|
770 |
|
771 | this.removeListener('data', socketOnData);
|
772 | this[kWebSocket] = undefined;
|
773 |
|
774 | clearTimeout(websocket._closeTimer);
|
775 |
|
776 | if (
|
777 | websocket._receiver._writableState.finished ||
|
778 | websocket._receiver._writableState.errorEmitted
|
779 | ) {
|
780 | websocket.emitClose();
|
781 | } else {
|
782 | websocket._receiver.on('error', receiverOnFinish);
|
783 | websocket._receiver.on('finish', receiverOnFinish);
|
784 | }
|
785 | }
|
786 |
|
787 |
|
788 |
|
789 |
|
790 |
|
791 |
|
792 |
|
793 | function socketOnData (chunk) {
|
794 | if (!this[kWebSocket]._receiver.write(chunk)) {
|
795 | this.pause();
|
796 | }
|
797 | }
|
798 |
|
799 |
|
800 |
|
801 |
|
802 |
|
803 |
|
804 | function socketOnEnd () {
|
805 | const websocket = this[kWebSocket];
|
806 |
|
807 | websocket.readyState = WebSocket.CLOSING;
|
808 | websocket._receiver.end();
|
809 | this.end();
|
810 | }
|
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 |
|
817 | function socketOnError () {
|
818 | const websocket = this[kWebSocket];
|
819 |
|
820 | this.removeListener('error', socketOnError);
|
821 | this.on('error', constants.NOOP);
|
822 |
|
823 | if (websocket) {
|
824 | websocket.readyState = WebSocket.CLOSING;
|
825 | this.destroy();
|
826 | }
|
827 | }
|