UNPKG

@quartic/bokehjs

Version:

Interactive, novel data visualization

537 lines (536 loc) 21.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ClientConnection, ClientSession, Message, message_handlers; var es6_promise_1 = require("es6-promise"); var logging_1 = require("./core/logging"); var string_1 = require("./core/util/string"); var object_1 = require("./core/util/object"); var document_1 = require("./document"); exports.DEFAULT_SERVER_WEBSOCKET_URL = "ws://localhost:5006/ws"; exports.DEFAULT_SESSION_ID = "default"; Message = (function () { function Message(header1, metadata1, content1) { this.header = header1; this.metadata = metadata1; this.content = content1; this.buffers = []; } Message.assemble = function (header_json, metadata_json, content_json) { var content, e, header, metadata; try { header = JSON.parse(header_json); metadata = JSON.parse(metadata_json); content = JSON.parse(content_json); return new Message(header, metadata, content); } catch (error1) { e = error1; logging_1.logger.error("Failure parsing json " + e + " " + header_json + " " + metadata_json + " " + content_json, e); throw e; } }; Message.create_header = function (msgtype, options) { var header; header = { 'msgid': string_1.uniqueId(), 'msgtype': msgtype }; return object_1.extend(header, options); }; Message.create = function (msgtype, header_options, content) { var header; if (content == null) { content = {}; } header = Message.create_header(msgtype, header_options); return new Message(header, {}, content); }; Message.prototype.send = function (socket) { var content_json, e, header_json, metadata_json; try { header_json = JSON.stringify(this.header); metadata_json = JSON.stringify(this.metadata); content_json = JSON.stringify(this.content); socket.send(header_json); socket.send(metadata_json); return socket.send(content_json); } catch (error1) { e = error1; logging_1.logger.error("Error sending ", this, e); throw e; } }; Message.prototype.complete = function () { if ((this.header != null) && (this.metadata != null) && (this.content != null)) { if ('num_buffers' in this.header) { return this.buffers.length === this.header['num_buffers']; } else { return true; } } else { return false; } }; Message.prototype.add_buffer = function (buffer) { return this.buffers.push(buffer); }; Message.prototype._header_field = function (field) { if (field in this.header) { return this.header[field]; } else { return null; } }; Message.prototype.msgid = function () { return this._header_field('msgid'); }; Message.prototype.msgtype = function () { return this._header_field('msgtype'); }; Message.prototype.sessid = function () { return this._header_field('sessid'); }; Message.prototype.reqid = function () { return this._header_field('reqid'); }; Message.prototype.problem = function () { if (!('msgid' in this.header)) { return "No msgid in header"; } else if (!('msgtype' in this.header)) { return "No msgtype in header"; } else { return null; } }; return Message; })(); message_handlers = { 'PATCH-DOC': function (connection, message) { return connection._for_session(function (session) { return session._handle_patch(message); }); }, 'OK': function (connection, message) { return logging_1.logger.debug("Unhandled OK reply to " + (message.reqid())); }, 'ERROR': function (connection, message) { return logging_1.logger.error("Unhandled ERROR reply to " + (message.reqid()) + ": " + message.content['text']); } }; ClientConnection = (function () { ClientConnection._connection_count = 0; function ClientConnection(url1, id, args_string1, _on_have_session_hook, _on_closed_permanently_hook) { this.url = url1; this.id = id; this.args_string = args_string1; this._on_have_session_hook = _on_have_session_hook; this._on_closed_permanently_hook = _on_closed_permanently_hook; this._number = ClientConnection._connection_count; ClientConnection._connection_count = this._number + 1; if (this.url == null) { this.url = exports.DEFAULT_SERVER_WEBSOCKET_URL; } if (this.id == null) { this.id = exports.DEFAULT_SESSION_ID; } logging_1.logger.debug("Creating websocket " + this._number + " to '" + this.url + "' session '" + this.id + "'"); this.socket = null; this.closed_permanently = false; this._fragments = []; this._partial = null; this._current_handler = null; this._pending_ack = null; this._pending_replies = {}; this.session = null; } ClientConnection.prototype._for_session = function (f) { if (this.session !== null) { return f(this.session); } }; ClientConnection.prototype.connect = function () { var error, ref, versioned_url; if (this.closed_permanently) { return es6_promise_1.Promise.reject(new Error("Cannot connect() a closed ClientConnection")); } if (this.socket != null) { return es6_promise_1.Promise.reject(new Error("Already connected")); } this._fragments = []; this._partial = null; this._pending_replies = {}; this._current_handler = null; try { versioned_url = this.url + "?bokeh-protocol-version=1.0&bokeh-session-id=" + this.id; if (((ref = this.args_string) != null ? ref.length : void 0) > 0) { versioned_url += "&" + this.args_string; } if (window.MozWebSocket != null) { this.socket = new MozWebSocket(versioned_url); } else { this.socket = new WebSocket(versioned_url); } return new es6_promise_1.Promise((function (_this) { return function (resolve, reject) { _this.socket.binarytype = "arraybuffer"; _this.socket.onopen = function () { return _this._on_open(resolve, reject); }; _this.socket.onmessage = function (event) { return _this._on_message(event); }; _this.socket.onclose = function (event) { return _this._on_close(event); }; return _this.socket.onerror = function () { return _this._on_error(reject); }; }; })(this)); } catch (error1) { error = error1; logging_1.logger.error("websocket creation failed to url: " + this.url); logging_1.logger.error(" - " + error); return es6_promise_1.Promise.reject(error); } }; ClientConnection.prototype.close = function () { if (!this.closed_permanently) { logging_1.logger.debug("Permanently closing websocket connection " + this._number); this.closed_permanently = true; if (this.socket != null) { this.socket.close(1000, "close method called on ClientConnection " + this._number); } this._for_session(function (session) { return session._connection_closed(); }); if (this._on_closed_permanently_hook != null) { this._on_closed_permanently_hook(); return this._on_closed_permanently_hook = null; } } }; ClientConnection.prototype._schedule_reconnect = function (milliseconds) { var retry; retry = (function (_this) { return function () { if (true || _this.closed_permanently) { if (!_this.closed_permanently) { logging_1.logger.info("Websocket connection " + _this._number + " disconnected, will not attempt to reconnect"); } } else { logging_1.logger.debug("Attempting to reconnect websocket " + _this._number); return _this.connect(); } }; })(this); return setTimeout(retry, milliseconds); }; ClientConnection.prototype.send = function (message) { var e; try { if (this.socket === null) { throw new Error("not connected so cannot send " + message); } return message.send(this.socket); } catch (error1) { e = error1; return logging_1.logger.error("Error sending message ", e, message); } }; ClientConnection.prototype.send_event = function (event) { var message; message = Message.create('EVENT', {}, JSON.stringify(event)); return this.send(message); }; ClientConnection.prototype.send_with_reply = function (message) { var promise; promise = new es6_promise_1.Promise((function (_this) { return function (resolve, reject) { _this._pending_replies[message.msgid()] = [resolve, reject]; return _this.send(message); }; })(this)); return promise.then(function (message) { if (message.msgtype() === 'ERROR') { throw new Error("Error reply " + message.content['text']); } else { return message; } }, function (error) { throw error; }); }; ClientConnection.prototype._pull_doc_json = function () { var message, promise; message = Message.create('PULL-DOC-REQ', {}); promise = this.send_with_reply(message); return promise.then(function (reply) { if (!('doc' in reply.content)) { throw new Error("No 'doc' field in PULL-DOC-REPLY"); } return reply.content['doc']; }, function (error) { throw error; }); }; ClientConnection.prototype._repull_session_doc = function () { if (this.session === null) { logging_1.logger.debug("Pulling session for first time"); } else { logging_1.logger.debug("Repulling session"); } return this._pull_doc_json().then((function (_this) { return function (doc_json) { var document, patch, patch_message; if (_this.session === null) { if (_this.closed_permanently) { return logging_1.logger.debug("Got new document after connection was already closed"); } else { document = document_1.Document.from_json(doc_json); patch = document_1.Document._compute_patch_since_json(doc_json, document); if (patch.events.length > 0) { logging_1.logger.debug("Sending " + patch.events.length + " changes from model construction back to server"); patch_message = Message.create('PATCH-DOC', {}, patch); _this.send(patch_message); } _this.session = new ClientSession(_this, document, _this.id); logging_1.logger.debug("Created a new session from new pulled doc"); if (_this._on_have_session_hook != null) { _this._on_have_session_hook(_this.session); return _this._on_have_session_hook = null; } } } else { _this.session.document.replace_with_json(doc_json); return logging_1.logger.debug("Updated existing session with new pulled doc"); } }; })(this), function (error) { throw error; })["catch"](function (error) { if (console.trace != null) { console.trace(error); } return logging_1.logger.error("Failed to repull session " + error); }); }; ClientConnection.prototype._on_open = function (resolve, reject) { logging_1.logger.info("Websocket connection " + this._number + " is now open"); this._pending_ack = [resolve, reject]; return this._current_handler = (function (_this) { return function (message) { return _this._awaiting_ack_handler(message); }; })(this); }; ClientConnection.prototype._on_message = function (event) { var e; try { return this._on_message_unchecked(event); } catch (error1) { e = error1; return logging_1.logger.error("Error handling message: " + e + ", " + event); } }; ClientConnection.prototype._on_message_unchecked = function (event) { var msg, problem; if (this._current_handler == null) { logging_1.logger.error("got a message but haven't set _current_handler"); } if (event.data instanceof ArrayBuffer) { if ((this._partial != null) && !this._partial.complete()) { this._partial.add_buffer(event.data); } else { this._close_bad_protocol("Got binary from websocket but we were expecting text"); } } else if (this._partial != null) { this._close_bad_protocol("Got text from websocket but we were expecting binary"); } else { this._fragments.push(event.data); if (this._fragments.length === 3) { this._partial = Message.assemble(this._fragments[0], this._fragments[1], this._fragments[2]); this._fragments = []; problem = this._partial.problem(); if (problem !== null) { this._close_bad_protocol(problem); } } } if ((this._partial != null) && this._partial.complete()) { msg = this._partial; this._partial = null; return this._current_handler(msg); } }; ClientConnection.prototype._on_close = function (event) { var pop_pending, promise_funcs; logging_1.logger.info("Lost websocket " + this._number + " connection, " + event.code + " (" + event.reason + ")"); this.socket = null; if (this._pending_ack != null) { this._pending_ack[1](new Error("Lost websocket connection, " + event.code + " (" + event.reason + ")")); this._pending_ack = null; } pop_pending = (function (_this) { return function () { var promise_funcs, ref, reqid; ref = _this._pending_replies; for (reqid in ref) { promise_funcs = ref[reqid]; delete _this._pending_replies[reqid]; return promise_funcs; } return null; }; })(this); promise_funcs = pop_pending(); while (promise_funcs !== null) { promise_funcs[1]("Disconnected"); promise_funcs = pop_pending(); } if (!this.closed_permanently) { return this._schedule_reconnect(2000); } }; ClientConnection.prototype._on_error = function (reject) { logging_1.logger.debug("Websocket error on socket " + this._number); return reject(new Error("Could not open websocket")); }; ClientConnection.prototype._close_bad_protocol = function (detail) { logging_1.logger.error("Closing connection: " + detail); if (this.socket != null) { return this.socket.close(1002, detail); } }; ClientConnection.prototype._awaiting_ack_handler = function (message) { if (message.msgtype() === "ACK") { this._current_handler = (function (_this) { return function (message) { return _this._steady_state_handler(message); }; })(this); this._repull_session_doc(); if (this._pending_ack != null) { this._pending_ack[0](this); return this._pending_ack = null; } } else { return this._close_bad_protocol("First message was not an ACK"); } }; ClientConnection.prototype._steady_state_handler = function (message) { var promise_funcs; if (message.reqid() in this._pending_replies) { promise_funcs = this._pending_replies[message.reqid()]; delete this._pending_replies[message.reqid()]; return promise_funcs[0](message); } else if (message.msgtype() in message_handlers) { return message_handlers[message.msgtype()](this, message); } else { return logging_1.logger.debug("Doing nothing with message " + (message.msgtype())); } }; return ClientConnection; })(); ClientSession = (function () { function ClientSession(_connection, document1, id) { this._connection = _connection; this.document = document1; this.id = id; this.document_listener = (function (_this) { return function (event) { return _this._document_changed(event); }; })(this); this.document.on_change(this.document_listener); this.event_manager = this.document.event_manager; this.event_manager.session = this; } ClientSession.prototype.close = function () { return this._connection.close(); }; ClientSession.prototype.send_event = function (type) { return this._connection.send_event(type); }; ClientSession.prototype._connection_closed = function () { return this.document.remove_on_change(this.document_listener); }; ClientSession.prototype.request_server_info = function () { var message, promise; message = Message.create('SERVER-INFO-REQ', {}); promise = this._connection.send_with_reply(message); return promise.then(function (reply) { return reply.content; }); }; ClientSession.prototype.force_roundtrip = function () { return this.request_server_info().then(function (ignored) { return void 0; }); }; ClientSession.prototype._document_changed = function (event) { var patch; if (event.setter_id === this.id) { return; } if (event instanceof document_1.ModelChangedEvent && !(event.attr in event.model.serializable_attributes())) { return; } patch = Message.create('PATCH-DOC', {}, this.document.create_json_patch([event])); return this._connection.send(patch); }; ClientSession.prototype._handle_patch = function (message) { return this.document.apply_json_patch(message.content, this.id); }; return ClientSession; })(); exports.pull_session = function (url, session_id, args_string) { var connection, promise, rejecter; rejecter = null; connection = null; promise = new es6_promise_1.Promise(function (resolve, reject) { connection = new ClientConnection(url, session_id, args_string, function (session) { var e; try { return resolve(session); } catch (error1) { e = error1; logging_1.logger.error("Promise handler threw an error, closing session " + error); session.close(); throw e; } }, function () { return reject(new Error("Connection was closed before we successfully pulled a session")); }); return connection.connect().then(function (whatever) { }, function (error) { logging_1.logger.error("Failed to connect to Bokeh server " + error); throw error; }); }); promise.close = function () { return connection.close(); }; return promise; };