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