LP#1268619: websocket JS additions
authorBill Erickson <berick@esilibrary.com>
Thu, 8 Nov 2012 17:36:16 +0000 (12:36 -0500)
committerGalen Charlton <gmc@esilibrary.com>
Tue, 19 Aug 2014 22:50:47 +0000 (15:50 -0700)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>

src/javascript/opensrf.js
src/javascript/opensrf_ws.js

index 408f0af..813bef8 100644 (file)
@@ -206,9 +206,8 @@ OpenSRF.Session = function() {
     this.state = OSRF_APP_SESSION_DISCONNECTED;
 };
 
-OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS;
-if (true || typeof WebSocket == 'undefined')
-    OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_XHR;
+//OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS;
+OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_XHR;
 
 OpenSRF.Session.cache = {};
 OpenSRF.Session.find_session = function(thread_trace) {
@@ -222,7 +221,7 @@ OpenSRF.Session.prototype.send = function(osrf_msg, args) {
     args = (args) ? args : {};
     switch(OpenSRF.Session.transport) {
         case OSRF_TRANSPORT_TYPE_WS:
-            return this.send_ws(osrf_msg, args);
+            return this.send_ws(osrf_msg);
         case OSRF_TRANSPORT_TYPE_XHR:
             return this.send_xhr(osrf_msg, args);
         case OSRF_TRANSPORT_TYPE_XMPP:
@@ -237,18 +236,11 @@ OpenSRF.Session.prototype.send_xhr = function(osrf_msg, args) {
     new OpenSRF.XHRequest(osrf_msg, args).send();
 };
 
-OpenSRF.Session.prototype.send_ws = function(osrf_msg, args) {
-    args.session = this;
-    if (this.websocket) {
-        this.websocket.args = args; // callbacks
-        this.websocket.send(osrf_msg);
-    } else {
-        this.websocket = new OpenSRF.WSRequest(
-            this, args, function(wsreq) {
-                wsreq.send(osrf_msg);
-            }
-        );
-    }
+OpenSRF.Session.prototype.send_ws = function(osrf_msg) {
+    new OpenSRF.WebSocketRequest(
+        this, 
+        function(wsreq) {wsreq.send(osrf_msg)} // onopen
+    );
 };
 
 OpenSRF.Session.prototype.send_xmpp = function(osrf_msg, args) {
@@ -262,9 +254,9 @@ OpenSRF.ClientSession = function(service) {
     this.remote_id = null;
     this.locale = OpenSRF.locale || 'en-US';
     this.last_id = 0;
-    this.thread = Math.random() + '' + new Date().getTime();
     this.requests = [];
     this.onconnect = null;
+    this.thread = Math.random() + '' + new Date().getTime();
     OpenSRF.Session.cache[this.thread] = this;
 };
 OpenSRF.set_subclass('OpenSRF.ClientSession', 'OpenSRF.Session');
@@ -412,11 +404,12 @@ OpenSRF.Request.prototype.send = function() {
     });
 };
 
-OpenSRF.NetMessage = function(to, from, thread, body) {
+OpenSRF.NetMessage = function(to, from, thread, body, osrf_msg) {
     this.to = to;
     this.from = from;
     this.thread = thread;
     this.body = body;
+    this.osrf_msg = osrf_msg;
 };
 
 OpenSRF.Stack = function() {
@@ -435,49 +428,59 @@ function log(msg) {
 }
 
 // ses may be passed to us by the network handler
-OpenSRF.Stack.push = function(net_msg, callbacks, ses) {
-    if (!ses) ses = OpenSRF.Session.find_session(net_msg.thread); 
+OpenSRF.Stack.push = function(net_msg, callbacks) {
+    var ses = OpenSRF.Session.find_session(net_msg.thread); 
     if (!ses) return;
     ses.remote_id = net_msg.from;
-    osrf_msgs = [];
 
-    try {
-        osrf_msgs = JSON2js(net_msg.body);
+    // NetMessage's from websocket connections are parsed before they get here
+    osrf_msgs = net_msg.osrf_msg;
 
-    } catch(E) {
-        log('Error parsing OpenSRF message body as JSON: ' + net_msg.body + '\n' + E);
-
-        /** UGH
-          * For unknown reasons, the Content-Type header will occasionally
-          * be included in the XHR.responseText for multipart/mixed messages.
-          * When this happens, strip the header and newlines from the message
-          * body and re-parse.
-          */
-        net_msg.body = net_msg.body.replace(/^.*\n\n/, '');
-        log('Cleaning up and retrying...');
+    if (!osrf_msgs) {
 
         try {
             osrf_msgs = JSON2js(net_msg.body);
-        } catch(E2) {
-            log('Unable to clean up message, giving up: ' + net_msg.body);
-            return;
+
+            if (OpenSRF.Session.transport == OSRF_TRANSPORT_TYPE_WS) {
+                // WebSocketRequests wrap the content
+                osrf_msgs = osrf_msgs.osrf_msg;
+            }
+
+        } catch(E) {
+            log('Error parsing OpenSRF message body as JSON: ' + net_msg.body + '\n' + E);
+
+            /** UGH
+              * For unknown reasons, the Content-Type header will occasionally
+              * be included in the XHR.responseText for multipart/mixed messages.
+              * When this happens, strip the header and newlines from the message
+              * body and re-parse.
+              */
+            net_msg.body = net_msg.body.replace(/^.*\n\n/, '');
+            log('Cleaning up and retrying...');
+
+            try {
+                osrf_msgs = JSON2js(net_msg.body);
+            } catch(E2) {
+                log('Unable to clean up message, giving up: ' + net_msg.body);
+                return;
+            }
         }
     }
 
     // push the latest responses onto the end of the inbound message queue
     for(var i = 0; i < osrf_msgs.length; i++)
-        OpenSRF.Stack.queue.push({msg : osrf_msgs[i], callbacks : callbacks, ses : ses});
+        OpenSRF.Stack.queue.push({msg : osrf_msgs[i], ses : ses});
 
     // continue processing responses, oldest to newest
     while(OpenSRF.Stack.queue.length) {
         var data = OpenSRF.Stack.queue.shift();
-        OpenSRF.Stack.handle_message(data.ses, data.msg, data.callbacks);
+        OpenSRF.Stack.handle_message(data.ses, data.msg);
     }
 };
 
-OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) {
+OpenSRF.Stack.handle_message = function(ses, osrf_msg) {
     
-    var req = null;
+    var req = ses.find_request(osrf_msg.threadTrace());
 
     if(osrf_msg.type() == OSRF_MESSAGE_TYPE_STATUS) {
 
@@ -486,12 +489,11 @@ OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) {
         var status_text = payload.status();
 
         if(status == OSRF_STATUS_COMPLETE) {
-            req = ses.find_request(osrf_msg.threadTrace());
             if(req) {
                 req.complete = true;
-                if(callbacks.oncomplete && !req.oncomplete_called) {
+                if(req.oncomplete && !req.oncomplete_called) {
                     req.oncomplete_called = true;
-                    return callbacks.oncomplete(req);
+                    return req.oncomplete(req);
                 }
             }
         }
@@ -507,18 +509,17 @@ OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) {
         }
 
         if(status == OSRF_STATUS_NOTFOUND || status == OSRF_STATUS_INTERNALSERVERERROR) {
-            req = ses.find_request(osrf_msg.threadTrace());
-            if(callbacks.onmethoderror) 
-                return callbacks.onmethoderror(req, status, status_text);
+            if(req && req.onmethoderror) 
+                return req.onmethoderror(req, status, status_text);
         }
     }
 
     if(osrf_msg.type() == OSRF_MESSAGE_TYPE_RESULT) {
-        req = ses.find_request(osrf_msg.threadTrace());
         if(req) {
             req.response_queue.push(osrf_msg.payload());
-            if(callbacks.onresponse) 
-                return callbacks.onresponse(req);
+            if(req.onresponse) {
+                return req.onresponse(req);
+            }
         }
     }
 };
index d522834..62302be 100644 (file)
  * GNU General Public License for more details.
  * ----------------------------------------------------------------------- */
 
-var WS_PATH = '/osrf-websocket';
+// opensrf defaults
+var WEBSOCKET_URL_PATH = '/osrf-websocket-translator';
+var WEBSOCKET_PORT = 7680;
+var WEBSOCKET_PORT_SSL = 7682;
+
+
+// Create the websocket and connect to the server
+// args.onopen is required
+// if args.default is true, use the default connection
+OpenSRF.WebSocketConnection = function(args, handlers) {
+    args = args || {};
+    this.handlers = handlers;
+
+    var secure = (args.ssl || location.protocol == 'https');
+    var path = args.path || WEBSOCKET_URL_PATH;
+    var port = args.port || (secure ? WEBSOCKET_PORT_SSL : WEBSOCKET_PORT);
+    var host = args.host || location.host;
+    var proto = (secure) ? 'wss' : 'ws';
+    this.path = proto + '://' + host + ':' + port + path;
+
+    this.setupSocket();
+    OpenSRF.WebSocketConnection.pool[args.name] = this;
+};
 
-/**
- * onopen is required. no data can be sent 
- * until the async connection dance completes.
- */
-OpenSRF.WSRequest = function(session, args, onopen) {
-    this.session = session;
-    this.args = args;
+// global pool of connection objects; name => connection map
+OpenSRF.WebSocketConnection.pool = {};
 
-    var proto = location.protocol == 'https' ? 'wss' : 'ws';
+OpenSRF.WebSocketConnection.defaultConnection = function() {
+    return OpenSRF.WebSocketConnection.pool['default'];
+}
 
-    var path = proto + '://' + location.host + 
-        WS_PATH + '?service=' + this.session.service;
+/**
+ * create a new WebSocket.  useful for new connections or 
+ * applying a new socket to an existing connection (whose 
+ * socket was disconnected)
+ */
+OpenSRF.WebSocketConnection.prototype.setupSocket = function() {
 
     try {
-        this.ws = new WebSocket(path);
+        this.socket = new WebSocket(this.path);
     } catch(e) {
-        throw new Error("WebSocket() not supported in this browser " + e);
-    }
-
-    var self = this;
-
-    this.ws.onopen = function(evt) {
-        onopen(self);
+        throw new Error("WebSocket() not supported in this browser: " + e);
     }
 
-    this.ws.onmessage = function(evt) {
-        self.core_handler(evt.data);
-    }
+    this.socket.onopen = this.handlers.onopen;
+    this.socket.onmessage = this.handlers.onmessage;
+    this.socket.onerror = this.handlers.onerror;
+    this.socket.onclose = this.handlers.onclose;
+};
 
-    this.ws.onerror = function(evt) {
-        self.transport_error_handler(evt.data);
-    }
+/** default onmessage handler: push the message up the opensrf stack */
+OpenSRF.WebSocketConnection.default_onmessage = function(evt) {
+    console.log('receiving: ' + evt.data);
+    var msg = JSON2js(evt.data);
+    OpenSRF.Stack.push(
+        new OpenSRF.NetMessage(
+            null, null, msg.thread, null, msg.osrf_msg)
+    );
+};
 
-    this.ws.onclose = function(evt) {
-    }
+/** default error handler */
+OpenSRF.WebSocketConnection.default_onerror = function(evt) {
+    throw new Error("WebSocket Error " + evt + ' : ' + evt.data);
 };
 
-OpenSRF.WSRequest.prototype.send = function(message) {
-    //console.log('sending: ' + js2JSON([message.serialize()]));
-    this.last_message = message;
-    this.ws.send(js2JSON([message.serialize()]));
-    return this;
+
+/** shut it down */
+OpenSRF.WebSocketConnection.prototype.destroy = function() {
+    this.socket.close();
+    delete OpenSRF.WebSocketConnection.pool[this.name];
 };
 
-OpenSRF.WSRequest.prototype.close = function() {
-    try { this.ws.close(); } catch(e) {}
+/**
+ * Creates the request object, but does not connect or send anything
+ * until the first call to send().
+ */
+OpenSRF.WebSocketRequest = function(session, onopen, connectionArgs) {
+    this.session = session;
+    this.onopen = onopen;
+    this.setupConnection(connectionArgs || {});
 }
 
-OpenSRF.WSRequest.prototype.core_handler = function(json) {
-    //console.log('received: ' + json);
+OpenSRF.WebSocketRequest.prototype.setupConnection = function(args) {
+    var self = this;
 
-    OpenSRF.Stack.push(
-        new OpenSRF.NetMessage(null, null, '', json),
-        {
-            onresponse : this.args.onresponse,
-            oncomplete : this.args.oncomplete,
-            onerror : this.args.onerror,
-            onmethoderror : this.method_error_handler()
-        },
-        this.args.session
-    );
-};
+    var cname = args.name || 'default';
+    this.wsc = OpenSRF.WebSocketConnection.pool[cname];
 
+    if (this.wsc) { // we have a WebSocketConnection.  
 
-OpenSRF.WSRequest.prototype.method_error_handler = function() {
-    var self = this;
-    return function(req, status, status_text) {
-        if(self.args.onmethoderror) 
-            self.args.onmethoderror(req, status, status_text);
+        switch (this.wsc.socket.readyState) {
+
+            case this.wsc.socket.CONNECTING:
+                // replace the original onopen handler with a new combined handler
+                var orig_open = this.wsc.socket.onopen;
+                this.wsc.socket.onopen = function() {
+                    orig_open();
+                    self.onopen(self);
+                };
+                break;
 
-        if(self.args.onerror)  {
-            self.args.onerror(
-                self.last_message, self.session.service, '');
+            case this.wsc.socket.OPEN:
+                // user is expecting an onopen event.  socket is 
+                // already open, so we have to manufacture one.
+                this.onopen(this);
+                break;
+
+            default:
+                console.log('WebSocket is no longer connecting; reconnecting');
+                this.wsc.setupSocket();
         }
-    };
-};
 
-OpenSRF.WSRequest.prototype.transport_error_handler = function(msg) {
-    if(this.args.ontransporterror) {
-        this.args.ontransporterror(msg);
-    }
-    if(this.args.onerror) {
-        this.args.onerror(msg, this.session.service, '');
+    } else { // no connection found
+
+        if (cname == 'default' || args.useDefaultHandlers) { // create the default handle 
+
+            this.wsc = new OpenSRF.WebSocketConnection(
+                {name : cname}, {
+                    onopen : function(evt) {if (self.onopen) self.onopen(self)},
+                    onmessage : OpenSRF.WebSocketConnection.default_onmessage,
+                    onerror : OpenSRF.WebSocketRequest.default_onerror,
+                    onclose : OpenSRF.WebSocketRequest.default_onclose
+                } 
+            );
+
+        } else {
+            throw new Error("No such WebSocketConnection '" + cname + "'");
+        }
     }
+}
+
+
+OpenSRF.WebSocketRequest.prototype.send = function(message) {
+    var wrapper = {
+        service : this.session.service,
+        thread : this.session.thread,
+        osrf_msg : [message.serialize()]
+    };
+
+    var json = js2JSON(wrapper);
+    console.log('sending: ' + json);
+
+    // drop it on the wire
+    this.wsc.socket.send(json);
+    return this;
 };
 
 
+
+