UNPKG

vue-bokeh

Version:

A wrapper around bokehjs embedded

606 lines (550 loc) 19.6 kB
var ClientConnection, ClientSession, DEFAULT_SERVER_WEBSOCKET_URL, DEFAULT_SESSION_ID, Document, HasProps, Message, ModelChangedEvent, Promise, RootAddedEvent, RootRemovedEvent, _, logger, message_handlers, pull_session, ref; _ = require("underscore"); Promise = require("es6-promise").Promise; HasProps = require("./core/has_props"); logger = require("./core/logging").logger; ref = require("./document"), Document = ref.Document, ModelChangedEvent = ref.ModelChangedEvent, RootAddedEvent = ref.RootAddedEvent, RootRemovedEvent = ref.RootRemovedEvent; DEFAULT_SERVER_WEBSOCKET_URL = "ws://localhost:5006/ws"; 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, error1, 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; 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': _.uniqueId(), 'msgtype': msgtype }; return _.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, error1, 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; 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 logger.debug("Unhandled OK reply to " + (message.reqid())); }, 'ERROR': function(connection, message) { return logger.error("Unhandled ERROR reply to " + (message.reqid()) + ": " + message.content['text']); } }; ClientConnection = (function() { ClientConnection._connection_count = 0; function ClientConnection(url1, id, _on_have_session_hook, _on_closed_permanently_hook) { this.url = url1; this.id = id; 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 = DEFAULT_SERVER_WEBSOCKET_URL; } if (this.id == null) { this.id = DEFAULT_SESSION_ID; } 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, error1, versioned_url; if (this.closed_permanently) { return Promise.reject(new Error("Cannot connect() a closed ClientConnection")); } if (this.socket != null) { return 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 (window.MozWebSocket != null) { this.socket = new MozWebSocket(versioned_url); } else { this.socket = new WebSocket(versioned_url); } return new 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; logger.error("websocket creation failed to url: " + this.url); logger.error(" - " + error); return Promise.reject(error); } }; ClientConnection.prototype.close = function() { if (!this.closed_permanently) { 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) { logger.info("Websocket connection " + _this._number + " disconnected, will not attempt to reconnect"); } } else { logger.debug("Attempting to reconnect websocket " + _this._number); return _this.connect(); } }; })(this); return setTimeout(retry, milliseconds); }; ClientConnection.prototype.send = function(message) { var e, error1; try { if (this.socket === null) { throw new Error("not connected so cannot send " + message); } return message.send(this.socket); } catch (error1) { e = error1; return logger.error("Error sending message ", e, message); } }; ClientConnection.prototype.send_with_reply = function(message) { var promise; promise = new 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) { logger.debug("Pulling session for first time"); } else { 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 logger.debug("Got new document after connection was already closed"); } else { document = Document.from_json(doc_json); patch = Document._compute_patch_since_json(doc_json, document); if (patch.events.length > 0) { 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); 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 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 logger.error("Failed to repull session " + error); }); }; ClientConnection.prototype._on_open = function(resolve, reject) { 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, error1; try { return this._on_message_unchecked(event); } catch (error1) { e = error1; return logger.error("Error handling message: " + e + ", " + event); } }; ClientConnection.prototype._on_message_unchecked = function(event) { var msg, problem; if (this._current_handler == null) { 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; 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() { var promise_funcs, ref1, reqid; ref1 = this._pending_replies; for (reqid in ref1) { promise_funcs = ref1[reqid]; delete this._pending_replies[reqid]; return promise_funcs; } return null; }; 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) { logger.debug("Websocket error on socket " + this._number); return reject(new Error("Could not open websocket")); }; ClientConnection.prototype._close_bad_protocol = function(detail) { 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 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._current_patch = null; this.document_listener = (function(_this) { return function(event) { return _this._document_changed(event); }; })(this); this.document.on_change(this.document_listener); } ClientSession.prototype.close = function() { return this._connection.close(); }; 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._should_suppress_on_change = function(patch, event) { var event_json, i, j, k, l, len, len1, len2, len3, patch_new, ref1, ref2, ref3, ref4; if (event instanceof ModelChangedEvent) { ref1 = patch.content['events']; for (i = 0, len = ref1.length; i < len; i++) { event_json = ref1[i]; if (event_json['kind'] === 'ModelChanged' && event_json['model']['id'] === event.model.id && event_json['attr'] === event.attr) { patch_new = event_json['new']; if (event.new_ instanceof HasProps) { if (typeof patch_new === 'object' && 'id' in patch_new && patch_new['id'] === event.new_.id) { return true; } } else if (_.isEqual(patch_new, event.new_)) { return true; } } } } else if (event instanceof RootAddedEvent) { ref2 = patch.content['events']; for (j = 0, len1 = ref2.length; j < len1; j++) { event_json = ref2[j]; if (event_json['kind'] === 'RootAdded' && event_json['model']['id'] === event.model.id) { return true; } } } else if (event instanceof RootRemovedEvent) { ref3 = patch.content['events']; for (k = 0, len2 = ref3.length; k < len2; k++) { event_json = ref3[k]; if (event_json['kind'] === 'RootRemoved' && event_json['model']['id'] === event.model.id) { return true; } } } else if (event instanceof TitleChangedEvent) { ref4 = patch.content['events']; for (l = 0, len3 = ref4.length; l < len3; l++) { event_json = ref4[l]; if (event_json['kind'] === 'TitleChanged' && event_json['title'] === event.title) { return true; } } } return false; }; ClientSession.prototype._document_changed = function(event) { var patch; if ((this._current_patch != null) && this._should_suppress_on_change(this._current_patch, event)) { return; } if (event instanceof 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) { this._current_patch = message; try { return this.document.apply_json_patch(message.content); } finally { this._current_patch = null; } }; return ClientSession; })(); pull_session = function(url, session_id) { var connection, promise, rejecter; rejecter = null; connection = null; promise = new Promise(function(resolve, reject) { connection = new ClientConnection(url, session_id, function(session) { var e, error1; try { return resolve(session); } catch (error1) { e = error1; 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) { logger.error("Failed to connect to Bokeh server " + error); throw error; }); }); promise.close = function() { return connection.close(); }; return promise; }; module.exports = { pull_session: pull_session, DEFAULT_SERVER_WEBSOCKET_URL: DEFAULT_SERVER_WEBSOCKET_URL, DEFAULT_SESSION_ID: DEFAULT_SESSION_ID };