UNPKG

websocket-async

Version:

An async/await WebSocket client for browsers

213 lines (172 loc) 7.42 kB
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * An asynchronous WebSocket client. * @example * // Set up connection. * const webSocketClient = new WebSocketClient; * // Connect. * await webSocketClient.connect('ws://www.example.com/'); * // Send is synchronous. * webSocketClient.send('Hello!'); * // Receive is asynchronous. * console.log(await webSocketClient.receive()); * // See if there are any more messages received. * if (webSocketClient.dataAvailable !== 0) { * console.log(await webSocketClient.receive()); * } * // Close the connection. * await webSocketClient.disconnect(); */ var WebSocketClient = function () { function WebSocketClient() { _classCallCheck(this, WebSocketClient); this._reset(); } /** * Whether a connection is currently open. * @returns true if the connection is open. */ _createClass(WebSocketClient, [{ key: 'connect', /** * Sets up a WebSocket connection to specified url. Resolves when the * connection is established. Can be called again to reconnect to any url. */ value: function connect(url, protocols) { var _this = this; return this.disconnect().then(function () { _this._reset(); _this._socket = new WebSocket(url, protocols); _this._socket.binaryType = 'arraybuffer'; return _this._setupListenersOnConnect(); }); } /** * Send data through the websocket. * Must be connected. See {@link #connected}. */ }, { key: 'send', value: function send(data) { if (!this.connected) { throw this._closeEvent || new Error('Not connected.'); } this._socket.send(data); } /** * Asynchronously receive data from the websocket. * Resolves immediately if there is buffered, unreceived data. * Otherwise, resolves with the next rececived message, * or rejects if disconnected. * @returns A promise that resolves with the data received. */ }, { key: 'receive', value: function receive() { var _this2 = this; if (this._receiveDataQueue.length !== 0) { return Promise.resolve(this._receiveDataQueue.shift()); } if (!this.connected) { return Promise.reject(this._closeEvent || new Error('Not connected.')); } var receivePromise = new Promise(function (resolve, reject) { _this2._receiveCallbacksQueue.push({ resolve: resolve, reject: reject }); }); return receivePromise; } /** * Initiates the close handshake if there is an active connection. * Returns a promise that will never reject. * The promise resolves once the WebSocket connection is closed. */ }, { key: 'disconnect', value: function disconnect(code, reason) { var _this3 = this; if (!this.connected) { return Promise.resolve(this._closeEvent); } return new Promise(function (resolve, reject) { // It's okay to call resolve/reject multiple times in a promise. var callbacks = { resolve: function resolve(dummy) { // Make sure this object always stays in the queue // until callbacks.reject() (which is resolve) is called. _this3._receiveCallbacksQueue.push(callbacks); }, reject: resolve }; _this3._receiveCallbacksQueue.push(callbacks); // After this, we will imminently get a close event. // Therefore, this promise will resolve. _this3._socket.close(code, reason); }); } /** * Sets up the event listeners, which do the bulk of the work. * @private */ }, { key: '_setupListenersOnConnect', value: function _setupListenersOnConnect() { var _this4 = this; var socket = this._socket; return new Promise(function (resolve, reject) { var handleMessage = function handleMessage(event) { var messageEvent = event; // The cast was necessary because Flow's libdef's don't contain // a MessageEventListener definition. if (_this4._receiveCallbacksQueue.length !== 0) { _this4._receiveCallbacksQueue.shift().resolve(messageEvent.data); return; } _this4._receiveDataQueue.push(messageEvent.data); }; var handleOpen = function handleOpen(event) { socket.addEventListener('message', handleMessage); socket.addEventListener('close', function (event) { _this4._closeEvent = event; // Whenever a close event fires, the socket is effectively dead. // It's impossible for more messages to arrive. // If there are any promises waiting for messages, reject them. while (_this4._receiveCallbacksQueue.length !== 0) { _this4._receiveCallbacksQueue.shift().reject(_this4._closeEvent); } }); resolve(); }; socket.addEventListener('error', reject); socket.addEventListener('open', handleOpen); }); } /** * @private */ }, { key: '_reset', value: function _reset() { this._receiveDataQueue = []; this._receiveCallbacksQueue = []; this._closeEvent = null; } }, { key: 'connected', get: function get() { // Checking != null also checks against undefined. return this._socket != null && this._socket.readyState === WebSocket.OPEN; } /** * The number of messages available to receive. * @returns The number of queued messages that can be retrieved with {@link #receive} */ }, { key: 'dataAvailable', get: function get() { return this._receiveDataQueue.length; } }]); return WebSocketClient; }(); export default WebSocketClient;