UNPKG

@metacell/geppetto-meta-client

Version:

Geppetto web frontend. Geppetto is an open-source platform to build web-based tools to visualize and simulate neuroscience data and models.

359 lines (343 loc) 13 kB
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* * * WebSocket class use for communication between client and server * * @author Jesus R. Martinez (jesus@metacell.us) */ import Resources from '@metacell/geppetto-meta-core/Resources'; import EventManager from '@metacell/geppetto-meta-client/common/EventManager'; var connectionInterval = 300; import pako from 'pako'; import FileSaver from 'file-saver'; var callbackHandler = {}; var messageTypes = { CLIENT_ID: "client_id", RECONNECTION_ERROR: "reconnection_error" }; /** * Web socket creation and communication */ export var MessageSocket = /*#__PURE__*/function () { function MessageSocket() { _classCallCheck(this, MessageSocket); _defineProperty(this, "socket", null); _defineProperty(this, "clientID", null); _defineProperty(this, "nextID", 0); // sets protocol to use for connection _defineProperty(this, "protocol", window.location.protocol === 'https:' ? "wss://" : "ws://"); // flag used to connect using ws protocol if wss failed _defineProperty(this, "failsafe", false); // vars used for reconnection _defineProperty(this, "attempts", 0); _defineProperty(this, "host", undefined); _defineProperty(this, "projectId", undefined); _defineProperty(this, "lostConnectionId", undefined); _defineProperty(this, "reconnectionLimit", 10); _defineProperty(this, "autoReconnectInterval", 5 * 1000); _defineProperty(this, "socketStatus", Resources.SocketStatus.CLOSE); this.connect = this.connect.bind(this); this.reconnect = this.reconnect.bind(this); this.send = this.send.bind(this); } return _createClass(MessageSocket, [{ key: "connect", value: function connect(host) { var _this = this; if (this.socket !== null) { delete this.socket; } if ('WebSocket' in window) { this.socket = new WebSocket(host); this.host = host; this.socket.binaryType = "arraybuffer"; } else if ('MozWebSocket' in window) { this.socket = new MozWebSocket(host); } else { console.log(Resources.WEBSOCKET_NOT_SUPPORTED, true); return; } this.socket.onopen = function (e) { console.log(Resources.WEBSOCKET_OPENED, true); /* * attach the handlers once socket is opened on the first connection * differently handle the reconnection scenario */ if (_this.lostConnectionId) { var parameters = {}; parameters["connectionID"] = _this.lostConnectionId; parameters["projectId"] = _this.projectId; _this.send("reconnect", parameters); _this.lostConnectionId = undefined; } // Reset the counter for reconnection _this.attempts = 0; _this.socketStatus = Resources.SocketStatus.OPEN; console.log("%c WebSocket Status - Opened ", 'background: #444; color: #bada55'); }; this.socket.onclose = function (e) { switch (e.code) { case 1000: _this.socketStatus = Resources.SocketStatus.CLOSE; console.log(Resources.WEBSOCKET_CLOSED, true); break; default: if (_this.lostConnectionId === undefined) { _this.lostConnectionId = _this.getClientID(); } _this.reconnect(e); } }; this.socket.onmessage = function (msg) { var messageData = msg.data; if (messageData == "ping") { return; } // if it's a binary (possibly compressed) then determine its type and process it if (messageData instanceof ArrayBuffer) { _this.processBinaryMessage(messageData); // otherwise, for a text message, parse it and notify listeners } else { // a non compressed message _this.parseAndNotify(messageData); } }; // Detects problems when connecting to Geppetto server this.socket.onerror = function (e) { var message = Resources.SERVER_CONNECTION_ERROR; /* * Attempt to connect using ws first time wss fails, * if ws fails too then don't try again and display info error window */ if (_this.failsafe) { _this.protocol = "ws://"; _this.failsafe = true; _this.connect(_this.protocol + window.location.host + '/' + GEPPETTO_CONFIGURATION.contextPath + '/GeppettoServlet'); } else { switch (e.code) { case 'ECONNREFUSED': console.log("%c WebSocket Status - Open connection error ", 'background: #000; color: red'); console.log(Resources.WEBSOCKET_CONNECTION_ERROR, true); break; case undefined: console.log("%c WebSocket Status - Open connection error ", 'background: #000; color: red'); console.log(Resources.WEBSOCKET_RECONNECTION, true); break; default: console.log("%c WebSocket Status - Closed ", 'background: #000; color: red'); _this.socketStatus = Resources.SocketStatus.CLOSE; break; } } }; } /** * Attempt to reconnect to the backend */ }, { key: "reconnect", value: function reconnect(e) { var _this2 = this; if (this.attempts < this.reconnectionLimit) { this.attempts++; this.socketStatus = Resources.SocketStatus.RECONNECTING; console.log("WebSocket Status - retry in ".concat(this.autoReconnectInterval, "ms"), e); setTimeout(function () { console.log("%c WebSocket Status - reconnecting... ", 'background: #444; color: #bada55'); _this2.connect(_this2.host); }, this.autoReconnectInterval); } else { this.socketStatus = Resources.SocketStatus.CLOSE; console.log(Resources.WEBSOCKET_CLOSED, true); EventManager.actionsHandler[EventManager.clientActions.WEBSOCKET_DISCONNECTED](); } } /** * Sends messages to the server */ }, { key: "send", value: function send(command, parameter, callback) { if (this.socketStatus === Resources.SocketStatus.RECONNECTING && command !== "reconnect") { EventManager.actionsHandler[EventManager.clientActions.STOP_LOGO](); return; } var requestID = this.createRequestID(); this.waitForConnection(messageTemplate(requestID, command, parameter), connectionInterval); // add callback with request id if any if (callback != undefined) { callbackHandler[requestID] = callback; } return requestID; } }, { key: "waitForConnection", value: function waitForConnection(messageTemplate, interval) { if (this.isReady() === 1) { this.socket.send(messageTemplate); } else if (this.isReady() > 1) { // connection is either closing (2) or already closed (3). EventManager.actionsHandler[EventManager.clientActions.WEBSOCKET_DISCONNECTED](); } else { // must be in connecting (0) state var that = this; setTimeout(function () { that.waitForConnection(messageTemplate); }, interval); } } }, { key: "isReady", value: function isReady() { if (this.socket !== null) { return this.socket.readyState; } else { return 0; } } }, { key: "close", value: function close() { this.socket.close(); EventManager.actionsHandler[EventManager.clientActions.WEBSOCKET_DISCONNECTED](); } /** * Sets the id of the client */ }, { key: "setClientID", value: function setClientID(id) { this.clientID = id; } /** * Sets the id of the client */ }, { key: "getClientID", value: function getClientID() { return this.clientID; } /** * Creates a request id to send with the message to the server */ }, { key: "createRequestID", value: function createRequestID() { return this.clientID + "-" + this.nextID++; } }, { key: "loadProjectFromId", value: function loadProjectFromId(projectId) { this.send("load_project_from_id", { projectId: projectId }); } }, { key: "loadProjectFromUrl", value: function loadProjectFromUrl(projectURL) { this.send("load_project_from_url", projectURL); } }, { key: "loadProjectFromContent", value: function loadProjectFromContent(content) { this.send("load_project_from_content", content); } }, { key: "gzipUncompress", value: function gzipUncompress(compressedMessage) { var messageBytes = new Uint8Array(compressedMessage); var message = pako.ungzip(messageBytes, { to: "string" }); return message; } /** * Dispatches through Redux actions all messages received from the socket * @param {*} messageData */ }, { key: "parseAndNotify", value: function parseAndNotify(messageData) { var parsedServerMessage = JSON.parse(messageData); console.debug("Received websocket message", parsedServerMessage); var payload = JSON.parse(parsedServerMessage.data); if (payload[parsedServerMessage.type]) { try { payload = JSON.parse(payload[parsedServerMessage.type]); } catch (e) { payload = payload[parsedServerMessage.type]; } } switch (parsedServerMessage.type) { case messageTypes.CLIENT_ID: { this.setClientID(payload.clientID); break; } case messageTypes.RECONNECTION_ERROR: { this.socketStatus = Resources.SocketStatus.CLOSE; break; } default: break; } EventManager.store.dispatch({ type: parsedServerMessage.type, data: payload }); // run callback if any if (parsedServerMessage.requestID != undefined) { if (callbackHandler[parsedServerMessage.requestID] != undefined) { callbackHandler[parsedServerMessage.requestID](parsedServerMessage.data); delete callbackHandler[parsedServerMessage.requestID]; } } } }, { key: "processBinaryMessage", value: function processBinaryMessage(message) { var messageBytes = new Uint8Array(message); /* * if it's a binary message and first byte it's zero then assume it's a compressed json string * otherwise is a file and a 'save as' dialog is opened */ if (messageBytes[0] == 0) { var message = pako.ungzip(messageBytes.subarray(1), { to: "string" }); this.parseAndNotify(message); } else { var fileNameLength = messageBytes[1]; var fileName = String.fromCharCode.apply(null, messageBytes.subarray(2, 2 + fileNameLength)); var blob = new Blob([message]); FileSaver.saveAs(blob.slice(2 + fileNameLength), fileName); } } }]); }(); /** * Template for Geppetto message * * @param msgtype - message type * @param payload - message payload, can be anything * @returns JSON stringified object */ function messageTemplate(id, msgtype, payload) { if (!(typeof payload == 'string' || payload instanceof String)) { payload = JSON.stringify(payload); } var object = { requestID: id, type: msgtype, data: payload }; return JSON.stringify(object); } export default new MessageSocket();