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');
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');
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.
24 * Class representing a WebSocket.
26 * @extends EventEmitter
28 class WebSocket extends EventEmitter {
30 * Create a new `WebSocket`.
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
36 constructor(address, protocols, options) {
39 this.readyState = WebSocket.CONNECTING;
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;
54 if (address !== null) {
55 if (Array.isArray(protocols)) {
56 protocols = protocols.join(', ');
57 } else if (typeof protocols === 'object' && protocols !== null) {
59 protocols = undefined;
62 initAsClient.call(this, address, protocols, options);
67 return WebSocket.CONNECTING;
70 return WebSocket.CLOSING;
73 return WebSocket.CLOSED;
76 return WebSocket.OPEN;
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).
86 return this._binaryType;
89 set binaryType(type) {
90 if (!constants.BINARY_TYPES.includes(type)) return;
92 this._binaryType = type;
95 // Allow to change `binaryType` on the fly.
97 if (this._receiver) this._receiver._binaryType = type;
103 get bufferedAmount() {
104 if (!this._socket) return 0;
107 // `socket.bufferSize` is `undefined` if the socket is closed.
109 return (this._socket.bufferSize || 0) + this._sender._bufferedBytes;
116 return Object.keys(this._extensions).join();
120 * Set up the socket and the internal resources.
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
127 setSocket(socket, head, maxPayload) {
128 const receiver = new Receiver(
134 this._sender = new Sender(socket, this._extensions);
135 this._receiver = receiver;
136 this._socket = socket;
138 receiver[kWebSocket] = this;
139 socket[kWebSocket] = this;
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);
148 socket.setTimeout(0);
151 if (head.length > 0) socket.unshift(head);
153 socket.on('close', socketOnClose);
154 socket.on('data', socketOnData);
155 socket.on('end', socketOnEnd);
156 socket.on('error', socketOnError);
158 this.readyState = WebSocket.OPEN;
163 * Emit the `'close'` event.
168 this.readyState = WebSocket.CLOSED;
171 this.emit('close', this._closeCode, this._closeMessage);
175 if (this._extensions[PerMessageDeflate.extensionName]) {
176 this._extensions[PerMessageDeflate.extensionName].cleanup();
179 this._receiver.removeAllListeners();
180 this.emit('close', this._closeCode, this._closeMessage);
184 * Start a closing handshake.
186 * +----------+ +-----------+ +----------+
187 * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
188 * | +----------+ +-----------+ +----------+ |
189 * +----------+ +-----------+ |
190 * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
191 * +----------+ +-----------+ |
193 * +------------------------+-->|fin| - - - -
195 * - - - - -|fin|<---------------------+
198 * @param {Number} code Status code explaining why the connection is closing
199 * @param {String} data A string explaining why the connection is closing
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);
209 if (this.readyState === WebSocket.CLOSING) {
210 if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
214 this.readyState = WebSocket.CLOSING;
215 this._sender.close(code, data, !this._isServer, (err) => {
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.
222 this._closeFrameSent = true;
224 if (this._socket.writable) {
225 if (this._closeFrameReceived) this._socket.end();
228 // Ensure that the connection is closed even if the closing handshake
231 this._closeTimer = setTimeout(
232 this._socket.destroy.bind(this._socket),
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
247 ping(data, mask, cb) {
248 if (typeof data === 'function') {
250 data = mask = undefined;
251 } else if (typeof mask === 'function') {
256 if (this.readyState !== WebSocket.OPEN) {
257 const err = new Error(
258 `WebSocket is not open: readyState ${this.readyState} ` +
259 `(${readyStates[this.readyState]})`
262 if (cb) return cb(err);
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);
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
279 pong(data, mask, cb) {
280 if (typeof data === 'function') {
282 data = mask = undefined;
283 } else if (typeof mask === 'function') {
288 if (this.readyState !== WebSocket.OPEN) {
289 const err = new Error(
290 `WebSocket is not open: readyState ${this.readyState} ` +
291 `(${readyStates[this.readyState]})`
294 if (cb) return cb(err);
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);
304 * Send a data message.
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
315 send(data, options, cb) {
316 if (typeof options === 'function') {
321 if (this.readyState !== WebSocket.OPEN) {
322 const err = new Error(
323 `WebSocket is not open: readyState ${this.readyState} ` +
324 `(${readyStates[this.readyState]})`
327 if (cb) return cb(err);
331 if (typeof data === 'number') data = data.toString();
333 const opts = Object.assign(
335 binary: typeof data !== 'string',
336 mask: !this._isServer,
343 if (!this._extensions[PerMessageDeflate.extensionName]) {
344 opts.compress = false;
347 this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
351 * Forcibly close the connection.
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);
363 this.readyState = WebSocket.CLOSING;
364 this._socket.destroy();
369 readyStates.forEach((readyState, i) => {
370 WebSocket[readyState] = i;
374 // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
375 // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
377 ['open', 'error', 'close', 'message'].forEach((method) => {
378 Object.defineProperty(WebSocket.prototype, `on${method}`, {
380 * Return the listener of the event.
382 * @return {(Function|undefined)} The event listener or `undefined`
386 const listeners = this.listeners(method);
387 for (var i = 0; i < listeners.length; i++) {
388 if (listeners[i]._listener) return listeners[i]._listener;
394 * Add a listener for the event.
396 * @param {Function} listener The listener to add
400 const listeners = this.listeners(method);
401 for (var i = 0; i < listeners.length; i++) {
403 // Remove only the listeners added via `addEventListener`.
405 if (listeners[i]._listener) this.removeListener(method, listeners[i]);
407 this.addEventListener(method, listener);
412 WebSocket.prototype.addEventListener = EventTarget.addEventListener;
413 WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
415 module.exports = WebSocket;
418 * Initialize a WebSocket client.
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
430 function initAsClient(address, protocols, options) {
431 options = Object.assign(
433 protocolVersion: protocolVersions[1],
434 perMessageDeflate: true,
435 maxPayload: 100 * 1024 * 1024
439 createConnection: undefined,
440 socketPath: undefined,
452 if (!protocolVersions.includes(options.protocolVersion)) {
453 throw new RangeError(
454 `Unsupported protocol version: ${options.protocolVersion} ` +
455 `(supported versions: ${protocolVersions.join(', ')})`
459 this._isServer = false;
463 if (typeof address === 'object' && address.href !== undefined) {
465 this.url = address.href;
468 // The WHATWG URL constructor is not available on Node.js < 6.13.0
470 parsedUrl = url.URL ? new url.URL(address) : url.parse(address);
474 const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
476 if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
477 throw new Error(`Invalid URL: ${this.url}`);
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;
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(
498 'Sec-WebSocket-Version': options.protocolVersion,
499 'Sec-WebSocket-Key': key,
500 Connection: 'Upgrade',
506 options.timeout = options.handshakeTimeout;
508 if (options.perMessageDeflate) {
509 perMessageDeflate = new PerMessageDeflate(
510 options.perMessageDeflate !== true ? options.perMessageDeflate : {},
514 options.headers['Sec-WebSocket-Extensions'] = extension.format({
515 [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
519 options.headers['Sec-WebSocket-Protocol'] = protocols;
521 if (options.origin) {
522 if (options.protocolVersion < 13) {
523 options.headers['Sec-WebSocket-Origin'] = options.origin;
525 options.headers.Origin = options.origin;
528 if (parsedUrl.auth) {
529 options.auth = parsedUrl.auth;
530 } else if (parsedUrl.username || parsedUrl.password) {
531 options.auth = `${parsedUrl.username}:${parsedUrl.password}`;
535 const parts = path.split(':');
537 options.socketPath = parts[0];
538 options.path = parts[1];
541 var req = (this._req = httpObj.get(options));
543 if (options.handshakeTimeout) {
544 req.on('timeout', () => {
545 abortHandshake(this, req, 'Opening handshake has timed out');
549 req.on('error', (err) => {
550 if (this._req.aborted) return;
552 req = this._req = null;
553 this.readyState = WebSocket.CLOSING;
554 this.emit('error', err);
558 req.on('response', (res) => {
559 if (this.emit('unexpected-response', req, res)) return;
561 abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`);
564 req.on('upgrade', (res, socket, head) => {
565 this.emit('upgrade', res);
568 // The user may have closed the connection from a listener of the `upgrade`
571 if (this.readyState !== WebSocket.CONNECTING) return;
573 req = this._req = null;
575 const digest = crypto
577 .update(key + constants.GUID, 'binary')
580 if (res.headers['sec-websocket-accept'] !== digest) {
581 abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header');
585 const serverProt = res.headers['sec-websocket-protocol'];
586 const protList = (protocols || '').split(/, */);
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';
598 abortHandshake(this, socket, protError);
602 if (serverProt) this.protocol = serverProt;
604 if (perMessageDeflate) {
606 const extensions = extension.parse(
607 res.headers['sec-websocket-extensions']
610 if (extensions[PerMessageDeflate.extensionName]) {
611 perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
612 this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
615 abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header');
620 this.setSocket(socket, head, options.maxPayload);
625 * Create a `net.Socket` and initiate a connection.
627 * @param {Object} options Connection options
628 * @return {net.Socket} The newly created socket used to start the connection
631 function netConnect(options) {
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.
638 if (options.protocolVersion) options.path = options.socketPath;
639 return net.connect(options);
643 * Create a `tls.TLSSocket` and initiate a connection.
645 * @param {Object} options Connection options
646 * @return {tls.TLSSocket} The newly created socket used to start the connection
649 function tlsConnect(options) {
650 options.path = undefined;
651 options.servername = options.servername || options.host;
652 return tls.connect(options);
656 * Abort the handshake and emit an error.
658 * @param {WebSocket} websocket The WebSocket instance
659 * @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
661 * @param {String} message The error message
664 function abortHandshake(websocket, stream, message) {
665 websocket.readyState = WebSocket.CLOSING;
667 const err = new Error(message);
668 Error.captureStackTrace(err, abortHandshake);
670 if (stream.setHeader) {
672 stream.once('abort', websocket.emitClose.bind(websocket));
673 websocket.emit('error', err);
676 stream.once('error', websocket.emit.bind(websocket, 'error'));
677 stream.once('close', websocket.emitClose.bind(websocket));
682 * The listener of the `Receiver` `'conclude'` event.
684 * @param {Number} code The status code
685 * @param {String} reason The reason for closing
688 function receiverOnConclude(code, reason) {
689 const websocket = this[kWebSocket];
691 websocket._socket.removeListener('data', socketOnData);
692 websocket._socket.resume();
694 websocket._closeFrameReceived = true;
695 websocket._closeMessage = reason;
696 websocket._closeCode = code;
698 if (code === 1005) websocket.close();
699 else websocket.close(code, reason);
703 * The listener of the `Receiver` `'drain'` event.
707 function receiverOnDrain() {
708 this[kWebSocket]._socket.resume();
712 * The listener of the `Receiver` `'error'` event.
714 * @param {(RangeError|Error)} err The emitted error
717 function receiverOnError(err) {
718 const websocket = this[kWebSocket];
720 websocket._socket.removeListener('data', socketOnData);
722 websocket.readyState = WebSocket.CLOSING;
723 websocket._closeCode = err[constants.kStatusCode];
724 websocket.emit('error', err);
725 websocket._socket.destroy();
729 * The listener of the `Receiver` `'finish'` event.
733 function receiverOnFinish() {
734 this[kWebSocket].emitClose();
738 * The listener of the `Receiver` `'message'` event.
740 * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
743 function receiverOnMessage(data) {
744 this[kWebSocket].emit('message', data);
748 * The listener of the `Receiver` `'ping'` event.
750 * @param {Buffer} data The data included in the ping frame
753 function receiverOnPing(data) {
754 const websocket = this[kWebSocket];
756 websocket.pong(data, !websocket._isServer, constants.NOOP);
757 websocket.emit('ping', data);
761 * The listener of the `Receiver` `'pong'` event.
763 * @param {Buffer} data The data included in the pong frame
766 function receiverOnPong(data) {
767 this[kWebSocket].emit('pong', data);
771 * The listener of the `net.Socket` `'close'` event.
775 function socketOnClose() {
776 const websocket = this[kWebSocket];
778 this.removeListener('close', socketOnClose);
779 this.removeListener('end', socketOnEnd);
781 websocket.readyState = WebSocket.CLOSING;
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
793 websocket._socket.read();
794 websocket._receiver.end();
796 this.removeListener('data', socketOnData);
797 this[kWebSocket] = undefined;
799 clearTimeout(websocket._closeTimer);
802 websocket._receiver._writableState.finished ||
803 websocket._receiver._writableState.errorEmitted
805 websocket.emitClose();
807 websocket._receiver.on('error', receiverOnFinish);
808 websocket._receiver.on('finish', receiverOnFinish);
813 * The listener of the `net.Socket` `'data'` event.
815 * @param {Buffer} chunk A chunk of data
818 function socketOnData(chunk) {
819 if (!this[kWebSocket]._receiver.write(chunk)) {
825 * The listener of the `net.Socket` `'end'` event.
829 function socketOnEnd() {
830 const websocket = this[kWebSocket];
832 websocket.readyState = WebSocket.CLOSING;
833 websocket._receiver.end();
838 * The listener of the `net.Socket` `'error'` event.
842 function socketOnError() {
843 const websocket = this[kWebSocket];
845 this.removeListener('error', socketOnError);
846 this.on('error', constants.NOOP);
849 websocket.readyState = WebSocket.CLOSING;