LP#1666706: add --with-websockets-port configure option
authorGalen Charlton <gmc@equinoxinitiative.org>
Tue, 21 Feb 2017 21:21:23 +0000 (16:21 -0500)
committerGalen Charlton <gmc@equinoxinitiative.org>
Tue, 21 Feb 2017 22:20:09 +0000 (17:20 -0500)
This patch adds the ability to specify the WebSockets
port as a configure option, avoiding the need to manually
edit a couple JavaScript files if one chooses to proxy
WS traffic.

This patch also cleans up the set of JavaScript files that
are installed so that some test case files are not installed
by default.

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
.gitignore
README
configure.ac
src/Makefile.am
src/javascript/opensrf_ws.js [deleted file]
src/javascript/opensrf_ws.js.in [new file with mode: 0644]
src/javascript/opensrf_ws_shared.js [deleted file]
src/javascript/opensrf_ws_shared.js.in [new file with mode: 0644]

index d614afe..0170ebe 100644 (file)
@@ -42,6 +42,8 @@ src/java/.lib/
 src/java/Makefile
 src/java/Makefile.in
 src/java/opensrf.jar
+src/javascript/opensrf_ws.js
+src/javascript/opensrf_ws_shared.js
 src/jserver/.deps/
 src/jserver/Makefile
 src/jserver/Makefile.in
diff --git a/README b/README
index 6bca63a..1755a6d 100644 (file)
--- a/README
+++ b/README
@@ -123,6 +123,11 @@ By default, OpenSRF includes C, Perl, and JavaScript support.
 You can add the `--enable-python` option to the configure command
 to build Python support and `--enable-java` for Java support.
 
+If you are planning on proxying WebSockets traffic (see below), you
+can add `--with-websockets-port=443` to specify that WebSockets traffic
+will be going through port 443. Without that option, the default port
+is 7682.
+
 Installation instructions
 -------------------------
 
@@ -557,7 +562,8 @@ ln -s /etc/nginx/sites-available/osrf-ws-http-proxy /etc/nginx/sites-enabled/osr
 /etc/init.d/nginx start
 ---------------------------------------------------------------------------
 +
-6. Edit `<PREFIX>/javascript/opensrf_ws.js` and `<PREFIX>/javascript/opensrf_ws_shared.js`
+6. If you didn't run `configure` with the `--with-websockets-port=443` option,
+edit `<PREFIX>/javascript/opensrf_ws.js` and `<PREFIX>/javascript/opensrf_ws_shared.js`
    and change
 +
 [source, javascript]
index 76bc293..073d6f8 100644 (file)
@@ -208,6 +208,12 @@ AC_ARG_WITH([perlbase],
 [PERL_BASE=x])
 AC_SUBST([PERL_BASE])
 
+AC_ARG_WITH([websockets-port],
+[  --with-websockets-port=path             WebSockets port to use (default is 7682)],
+[WS_PORT=${withval}],
+[WS_PORT=7682])
+AC_SUBST([WS_PORT])
+
 # The following Apache version detection code is adapted from
 # http://www.gnu.org/software/autoconf-archive/ax_prog_apache.html
 # licensed under version 2 of the GNU General Public License, or
@@ -373,6 +379,8 @@ if test "x$OSRF_INSTALL_CORE" = "xtrue"; then
                         examples/multisession-test.pl
                         src/c-apps/Makefile
                         src/gateway/Makefile
+                        src/javascript/opensrf_ws.js
+                        src/javascript/opensrf_ws_shared.js
                         src/libopensrf/Makefile
                         src/perl/Makefile
                         src/ports/strn_compat/Makefile
index 7def8b1..9f75238 100644 (file)
@@ -36,7 +36,7 @@ endif
 if INSTALLJAVASCRIPT
 MAYBE_JAVASCRIPT = javascript
 jsdir = $(prefix)/lib/javascript
-js_SCRIPTS = javascript/DojoSRF.js javascript/JSON_v1.js javascript/md5.js javascript/opensrf.js javascript/opensrf_xhr.js javascript/opensrf_xmpp.js javascript/opensrf_ws_shared.js
+js_SCRIPTS = javascript/DojoSRF.js javascript/JSON_v1.js javascript/md5.js javascript/opensrf.js javascript/opensrf_xhr.js javascript/opensrf_xmpp.js javascript/opensrf_ws.js javascript/opensrf_ws_shared.js
 endif
 
 if BUILDCORE
@@ -68,7 +68,6 @@ install-exec-hook:
                        rm "$(DESTDIR)@sysconfdir@/$${f}.bak" ; \
                done; \
        fi
-       cp -r @srcdir@/javascript/* $(DESTDIR)$(jsdir)/
        if [ ! -e @bindir@/osrf_control ]; then \
                ln -s @bindir@/opensrf-perl.pl @bindir@/osrf_control; \
        fi
diff --git a/src/javascript/opensrf_ws.js b/src/javascript/opensrf_ws.js
deleted file mode 100644 (file)
index 74fc40e..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/* -----------------------------------------------------------------------
- * Copyright (C) 2014  Equinox Software, Inc.
- * Bill Erickson <berick@esilibrary.com>
- *  
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * ----------------------------------------------------------------------- */
-
-
-var WEBSOCKET_URL_PATH = '/osrf-websocket-translator';
-var WEBSOCKET_PORT_SSL = 7682;
-
-OpenSRF.WebSocket = function() {
-    this.pending_messages = [];
-}
-
-OpenSRF.WebSocket.prototype.connected = function() {
-    return (
-        this.socket && 
-        this.socket.readyState == this.socket.OPEN
-    );
-}
-
-/**
- * If our global socket is already open, use it.  Otherwise, queue the 
- * message for delivery after the socket is open.
- */
-OpenSRF.WebSocket.prototype.send = function(message) {
-    var self = this;
-
-    if (this.connected()) {
-        // this.socket connection is viable.  send our message now.
-        this.socket.send(message);
-        return;
-    }
-
-    // no viable connection. queue our outbound messages for future delivery.
-    this.pending_messages.push(message);
-    console.log('pending count ' + this.pending_messages.length);
-
-    if (this.socket && this.socket.readyState == this.socket.CONNECTING) {
-        // we are already in the middle of a setup call.  
-        // our queued message will be delivered after setup completes.
-        return;
-    }
-
-    // we have no websocket or an invalid websocket.  build a new one.
-
-    var path = 'wss://' + location.host + ':' + 
-        WEBSOCKET_PORT_SSL + WEBSOCKET_URL_PATH;
-
-    console.debug('connecting websocket to ' + path);
-
-    try {
-        this.socket = new WebSocket(path);
-    } catch(E) {
-        console.log('Error creating WebSocket for path ' + path + ' : ' + E);
-        throw new Error(E);
-    }
-
-    this.socket.onopen = function() {
-        console.debug('websocket.onopen()');
-        // deliver any queued messages
-        var msg;
-        console.log('pending count ' + self.pending_messages.length);
-        while ( (msg = self.pending_messages.shift()) )
-            self.socket.send(msg);
-    }
-
-    this.socket.onmessage = function(evt) {
-        self.onmessage(evt.data);
-    }
-
-    /**
-     * Websocket error handler.  This type of error indicates a probelem
-     * with the connection.  I.e. it's not port-specific. 
-     * Broadcast to all ports.
-     */
-    this.socket.onerror = function(evt) {
-        var err = "WebSocket Error " + evt + ' : ' + evt.data;
-        self.socket.close(); // connection is no good; reset.
-        throw new Error(err); 
-    }
-
-    /**
-     * Called when the websocket connection is closed.
-     *
-     * Once a websocket is closed, it will be re-opened the next time
-     * a message delivery attempt is made.  Clean up and prepare to reconnect.
-     */
-    this.socket.onclose = function() {
-        console.debug('closing websocket');
-        self.socket = null;
-        if (OpenSRF.onWebSocketClosed)
-            OpenSRF.onWebSocketClosed();
-    }
-}
diff --git a/src/javascript/opensrf_ws.js.in b/src/javascript/opensrf_ws.js.in
new file mode 100644 (file)
index 0000000..dca3cd3
--- /dev/null
@@ -0,0 +1,104 @@
+/* -----------------------------------------------------------------------
+ * Copyright (C) 2014  Equinox Software, Inc.
+ * Bill Erickson <berick@esilibrary.com>
+ *  
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ----------------------------------------------------------------------- */
+
+
+var WEBSOCKET_URL_PATH = '/osrf-websocket-translator';
+var WEBSOCKET_PORT_SSL = @WS_PORT@;
+
+OpenSRF.WebSocket = function() {
+    this.pending_messages = [];
+}
+
+OpenSRF.WebSocket.prototype.connected = function() {
+    return (
+        this.socket && 
+        this.socket.readyState == this.socket.OPEN
+    );
+}
+
+/**
+ * If our global socket is already open, use it.  Otherwise, queue the 
+ * message for delivery after the socket is open.
+ */
+OpenSRF.WebSocket.prototype.send = function(message) {
+    var self = this;
+
+    if (this.connected()) {
+        // this.socket connection is viable.  send our message now.
+        this.socket.send(message);
+        return;
+    }
+
+    // no viable connection. queue our outbound messages for future delivery.
+    this.pending_messages.push(message);
+    console.log('pending count ' + this.pending_messages.length);
+
+    if (this.socket && this.socket.readyState == this.socket.CONNECTING) {
+        // we are already in the middle of a setup call.  
+        // our queued message will be delivered after setup completes.
+        return;
+    }
+
+    // we have no websocket or an invalid websocket.  build a new one.
+
+    var path = 'wss://' + location.host + ':' + 
+        WEBSOCKET_PORT_SSL + WEBSOCKET_URL_PATH;
+
+    console.debug('connecting websocket to ' + path);
+
+    try {
+        this.socket = new WebSocket(path);
+    } catch(E) {
+        console.log('Error creating WebSocket for path ' + path + ' : ' + E);
+        throw new Error(E);
+    }
+
+    this.socket.onopen = function() {
+        console.debug('websocket.onopen()');
+        // deliver any queued messages
+        var msg;
+        console.log('pending count ' + self.pending_messages.length);
+        while ( (msg = self.pending_messages.shift()) )
+            self.socket.send(msg);
+    }
+
+    this.socket.onmessage = function(evt) {
+        self.onmessage(evt.data);
+    }
+
+    /**
+     * Websocket error handler.  This type of error indicates a probelem
+     * with the connection.  I.e. it's not port-specific. 
+     * Broadcast to all ports.
+     */
+    this.socket.onerror = function(evt) {
+        var err = "WebSocket Error " + evt + ' : ' + evt.data;
+        self.socket.close(); // connection is no good; reset.
+        throw new Error(err); 
+    }
+
+    /**
+     * Called when the websocket connection is closed.
+     *
+     * Once a websocket is closed, it will be re-opened the next time
+     * a message delivery attempt is made.  Clean up and prepare to reconnect.
+     */
+    this.socket.onclose = function() {
+        console.debug('closing websocket');
+        self.socket = null;
+        if (OpenSRF.onWebSocketClosed)
+            OpenSRF.onWebSocketClosed();
+    }
+}
diff --git a/src/javascript/opensrf_ws_shared.js b/src/javascript/opensrf_ws_shared.js
deleted file mode 100644 (file)
index ad30dda..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-/* -----------------------------------------------------------------------
- * Copyright (C) 2014  Equinox Software, Inc.
- * Bill Erickson <berick@esilibrary.com>
- *  
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * ----------------------------------------------------------------------- */
-
-
-/**
- * Shared WebSocket communication layer.  Each browser tab registers with
- * this code all inbound / outbound messages are delivered through a
- * single websocket connection managed within.
- *
- * Messages take the form : {action : my_action, message : my_message}
- * actions for tab-generated messages may be "message" or "close".
- * actions for messages generated within may be "message" or "error"
- */
-
-var WEBSOCKET_URL_PATH = '/osrf-websocket-translator';
-var WEBSOCKET_PORT_SSL = 7682;
-var WEBSOCKET_MAX_THREAD_PORT_CACHE_SIZE = 1000;
-
-/**
- * Collection of shared ports (browser tabs)
- */
-var connected_ports = {};
-
-/**
- * Each port gets a local identifier so we have an easy way to refer to 
- * it later.
- */
-var port_identifier = 0;
-
-// maps osrf message threads to a port index in connected_ports.
-// this is how we know which browser tab to deliver messages to.
-var thread_port_map = {}; 
-
-/**
- * Browser-global, shared websocket connection.
- */
-var websocket;
-
-/** 
- * Pending messages awaiting a successful websocket connection
- *
- * instead of asking the caller to pass messages after a connection
- * is made, queue the messages for the caller and deliver them
- * after the connection is established.
- */
-var pending_ws_messages = [];
-
-/** 
- * Deliver the message blob to the specified port (tab)
- */
-function send_msg_to_port(ident, msg) {
-    console.debug('sending msg to port ' + ident + ' : ' + msg.action);
-    try {
-        connected_ports[ident].postMessage(msg);
-    } catch(E) {
-        // some browsers (Opera) throw an exception when messaging
-        // a disconnected port.
-        console.debug('unable to send msg to port ' + ident);
-        delete connected_ports[ident];
-    }
-}
-
-/**
- * Send a message blob to all ports (tabs)
- */
-function broadcast(msg) {
-    for (var ident in connected_ports)
-      send_msg_to_port(ident, msg);
-}
-
-
-/**
- * Opens the websocket connection.
- *
- * If our global socket is already open, use it.  Otherwise, queue the 
- * message for delivery after the socket is open.
- */
-function send_to_websocket(message) {
-
-    if (websocket && websocket.readyState == websocket.OPEN) {
-        // websocket connection is viable.  send our message now.
-        websocket.send(message);
-        return;
-    }
-
-    // no viable connection. queue our outbound messages for future delivery.
-    pending_ws_messages.push(message);
-
-    if (websocket && websocket.readyState == websocket.CONNECTING) {
-        // we are already in the middle of a setup call.  
-        // our queued message will be delivered after setup completes.
-        return;
-    }
-
-    // we have no websocket or an invalid websocket.  build a new one.
-
-    // assume SSL at all times
-    var path = 'wss://' + location.host + ':' + 
-        WEBSOCKET_PORT_SSL + WEBSOCKET_URL_PATH;
-
-    console.debug('connecting websocket to ' + path);
-
-    try {
-        websocket = new WebSocket(path);
-    } catch(E) {
-        console.log('Error creating WebSocket for path ' + path + ' : ' + E);
-        throw new Error(E);
-    }
-
-    websocket.onopen = function() {
-        console.debug('websocket.onopen()');
-        // deliver any queued messages
-        var msg;
-        while ( (msg = pending_ws_messages.shift()) )
-            websocket.send(msg);
-
-    }
-
-    websocket.onmessage = function(evt) {
-        var message = evt.data;
-
-        // this is sort of a hack to avoid having to run JSON2js
-        // multiple times on the same message.  Hopefully match() is
-        // faster.  Note: We can't use JSON_v1 within a shared worker
-        // for marshalling messages, because it has no knowledge of
-        // application-level class hints in this environment.
-        var thread;
-        var match = message.match(/"thread":"(.*?)"/);
-        if (!match || !(thread = match[1])) {
-            throw new Error("Websocket message malformed; no thread: " + message);
-        }
-
-        console.debug('websocket received message for thread ' + thread);
-
-        var port_msg = {action: 'message', message : message};
-        var port_ident = thread_port_map[thread];
-
-        if (port_ident) {
-            send_msg_to_port(port_ident, port_msg);
-        } else {
-            // don't know who it's for, broadcast and let the ports
-            // sort it out for themselves.
-            broadcast(port_msg);
-        }
-
-        /* poor man's memory management.  We are not cleaning up our
-         * thread_port_map as we go, because that would require parsing
-         * and analyzing every message to look for opensrf statuses.  
-         * parsing messages adds overhead (see also above comments about
-         * JSON_v1.js).  So, instead, after the map has reached a certain 
-         * size, clear it.  If any pending messages are afield that depend 
-         * on the map, they will be broadcast to all ports on arrival 
-         * (see above).  Only the port expecting a message with the given 
-         * thread will honor the message, all other ports will drop it 
-         * silently.  We could just broadcastfor every messsage, but this 
-         * is presumably more efficient.
-         *
-         * If for some reason this fails to work as expected, we could add
-         * a new tab->ws message type for marking a thread as complete.
-         * My hunch is this will be faster, since it will require a lot
-         * fewer cross-tab messages overall.
-         */
-        if (Object.keys(thread_port_map).length > 
-                WEBSOCKET_MAX_THREAD_PORT_CACHE_SIZE) {
-            console.debug('resetting thread_port_map');
-            thread_port_map = {};
-        }
-    }
-
-    /**
-     * Websocket error handler.  This type of error indicates a probelem
-     * with the connection.  I.e. it's not port-specific. 
-     * Broadcast to all ports.
-     */
-    websocket.onerror = function(evt) {
-        var err = "WebSocket Error " + evt;
-        console.error(err);
-        broadcast({action : 'event', type : 'onerror', message : err});
-        websocket.close(); // connection is no good; reset.
-    }
-
-    /**
-     * Called when the websocket connection is closed.
-     *
-     * Once a websocket is closed, it will be re-opened the next time
-     * a message delivery attempt is made.  Clean up and prepare to reconnect.
-     */
-    websocket.onclose = function() {
-        console.debug('closing websocket');
-        websocket = null;
-        thread_port_map = {};
-        broadcast({action : 'event', type : 'onclose'});
-    }
-}
-
-/**
- * New port (tab) opened handler
- *
- * Apply the port identifier and message handlers.
- */
-onconnect = function(e) {
-    var port = e.ports[0];
-
-    // we have no way of identifying ports within the message handler,
-    // so we apply an identifier to each and toss that into a closer.
-    var port_ident = port_identifier++;
-    connected_ports[port_ident] = port;
-
-    // message handler
-    port.addEventListener('message', function(e) {
-        var data = e.data;
-
-        if (data.action == 'message') {
-            thread_port_map[data.thread] = port_ident;
-            send_to_websocket(data.message);
-            return;
-        } 
-
-        if (messsage.action == 'close') {
-            // TODO: all browser tabs need an onunload handler which sends
-            // a action=close message, so that the port may be removed from
-            // the conected_ports collection.
-            delete connected_ports[port_ident];
-            console.debug('closed port ' + port_ident + 
-                '; ' + Object.keys(connected_ports).length + ' remaining');
-            return;
-        }
-
-    }, false);
-
-    port.start();
-
-    console.debug('added port ' + port_ident + 
-      '; ' + Object.keys(connected_ports).length + ' total');
-}
-
diff --git a/src/javascript/opensrf_ws_shared.js.in b/src/javascript/opensrf_ws_shared.js.in
new file mode 100644 (file)
index 0000000..06c8513
--- /dev/null
@@ -0,0 +1,248 @@
+/* -----------------------------------------------------------------------
+ * Copyright (C) 2014  Equinox Software, Inc.
+ * Bill Erickson <berick@esilibrary.com>
+ *  
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ----------------------------------------------------------------------- */
+
+
+/**
+ * Shared WebSocket communication layer.  Each browser tab registers with
+ * this code all inbound / outbound messages are delivered through a
+ * single websocket connection managed within.
+ *
+ * Messages take the form : {action : my_action, message : my_message}
+ * actions for tab-generated messages may be "message" or "close".
+ * actions for messages generated within may be "message" or "error"
+ */
+
+var WEBSOCKET_URL_PATH = '/osrf-websocket-translator';
+var WEBSOCKET_PORT_SSL = @WS_PORT@;
+var WEBSOCKET_MAX_THREAD_PORT_CACHE_SIZE = 1000;
+
+/**
+ * Collection of shared ports (browser tabs)
+ */
+var connected_ports = {};
+
+/**
+ * Each port gets a local identifier so we have an easy way to refer to 
+ * it later.
+ */
+var port_identifier = 0;
+
+// maps osrf message threads to a port index in connected_ports.
+// this is how we know which browser tab to deliver messages to.
+var thread_port_map = {}; 
+
+/**
+ * Browser-global, shared websocket connection.
+ */
+var websocket;
+
+/** 
+ * Pending messages awaiting a successful websocket connection
+ *
+ * instead of asking the caller to pass messages after a connection
+ * is made, queue the messages for the caller and deliver them
+ * after the connection is established.
+ */
+var pending_ws_messages = [];
+
+/** 
+ * Deliver the message blob to the specified port (tab)
+ */
+function send_msg_to_port(ident, msg) {
+    console.debug('sending msg to port ' + ident + ' : ' + msg.action);
+    try {
+        connected_ports[ident].postMessage(msg);
+    } catch(E) {
+        // some browsers (Opera) throw an exception when messaging
+        // a disconnected port.
+        console.debug('unable to send msg to port ' + ident);
+        delete connected_ports[ident];
+    }
+}
+
+/**
+ * Send a message blob to all ports (tabs)
+ */
+function broadcast(msg) {
+    for (var ident in connected_ports)
+      send_msg_to_port(ident, msg);
+}
+
+
+/**
+ * Opens the websocket connection.
+ *
+ * If our global socket is already open, use it.  Otherwise, queue the 
+ * message for delivery after the socket is open.
+ */
+function send_to_websocket(message) {
+
+    if (websocket && websocket.readyState == websocket.OPEN) {
+        // websocket connection is viable.  send our message now.
+        websocket.send(message);
+        return;
+    }
+
+    // no viable connection. queue our outbound messages for future delivery.
+    pending_ws_messages.push(message);
+
+    if (websocket && websocket.readyState == websocket.CONNECTING) {
+        // we are already in the middle of a setup call.  
+        // our queued message will be delivered after setup completes.
+        return;
+    }
+
+    // we have no websocket or an invalid websocket.  build a new one.
+
+    // assume SSL at all times
+    var path = 'wss://' + location.host + ':' + 
+        WEBSOCKET_PORT_SSL + WEBSOCKET_URL_PATH;
+
+    console.debug('connecting websocket to ' + path);
+
+    try {
+        websocket = new WebSocket(path);
+    } catch(E) {
+        console.log('Error creating WebSocket for path ' + path + ' : ' + E);
+        throw new Error(E);
+    }
+
+    websocket.onopen = function() {
+        console.debug('websocket.onopen()');
+        // deliver any queued messages
+        var msg;
+        while ( (msg = pending_ws_messages.shift()) )
+            websocket.send(msg);
+
+    }
+
+    websocket.onmessage = function(evt) {
+        var message = evt.data;
+
+        // this is sort of a hack to avoid having to run JSON2js
+        // multiple times on the same message.  Hopefully match() is
+        // faster.  Note: We can't use JSON_v1 within a shared worker
+        // for marshalling messages, because it has no knowledge of
+        // application-level class hints in this environment.
+        var thread;
+        var match = message.match(/"thread":"(.*?)"/);
+        if (!match || !(thread = match[1])) {
+            throw new Error("Websocket message malformed; no thread: " + message);
+        }
+
+        console.debug('websocket received message for thread ' + thread);
+
+        var port_msg = {action: 'message', message : message};
+        var port_ident = thread_port_map[thread];
+
+        if (port_ident) {
+            send_msg_to_port(port_ident, port_msg);
+        } else {
+            // don't know who it's for, broadcast and let the ports
+            // sort it out for themselves.
+            broadcast(port_msg);
+        }
+
+        /* poor man's memory management.  We are not cleaning up our
+         * thread_port_map as we go, because that would require parsing
+         * and analyzing every message to look for opensrf statuses.  
+         * parsing messages adds overhead (see also above comments about
+         * JSON_v1.js).  So, instead, after the map has reached a certain 
+         * size, clear it.  If any pending messages are afield that depend 
+         * on the map, they will be broadcast to all ports on arrival 
+         * (see above).  Only the port expecting a message with the given 
+         * thread will honor the message, all other ports will drop it 
+         * silently.  We could just broadcastfor every messsage, but this 
+         * is presumably more efficient.
+         *
+         * If for some reason this fails to work as expected, we could add
+         * a new tab->ws message type for marking a thread as complete.
+         * My hunch is this will be faster, since it will require a lot
+         * fewer cross-tab messages overall.
+         */
+        if (Object.keys(thread_port_map).length > 
+                WEBSOCKET_MAX_THREAD_PORT_CACHE_SIZE) {
+            console.debug('resetting thread_port_map');
+            thread_port_map = {};
+        }
+    }
+
+    /**
+     * Websocket error handler.  This type of error indicates a probelem
+     * with the connection.  I.e. it's not port-specific. 
+     * Broadcast to all ports.
+     */
+    websocket.onerror = function(evt) {
+        var err = "WebSocket Error " + evt;
+        console.error(err);
+        broadcast({action : 'event', type : 'onerror', message : err});
+        websocket.close(); // connection is no good; reset.
+    }
+
+    /**
+     * Called when the websocket connection is closed.
+     *
+     * Once a websocket is closed, it will be re-opened the next time
+     * a message delivery attempt is made.  Clean up and prepare to reconnect.
+     */
+    websocket.onclose = function() {
+        console.debug('closing websocket');
+        websocket = null;
+        thread_port_map = {};
+        broadcast({action : 'event', type : 'onclose'});
+    }
+}
+
+/**
+ * New port (tab) opened handler
+ *
+ * Apply the port identifier and message handlers.
+ */
+onconnect = function(e) {
+    var port = e.ports[0];
+
+    // we have no way of identifying ports within the message handler,
+    // so we apply an identifier to each and toss that into a closer.
+    var port_ident = port_identifier++;
+    connected_ports[port_ident] = port;
+
+    // message handler
+    port.addEventListener('message', function(e) {
+        var data = e.data;
+
+        if (data.action == 'message') {
+            thread_port_map[data.thread] = port_ident;
+            send_to_websocket(data.message);
+            return;
+        } 
+
+        if (messsage.action == 'close') {
+            // TODO: all browser tabs need an onunload handler which sends
+            // a action=close message, so that the port may be removed from
+            // the conected_ports collection.
+            delete connected_ports[port_ident];
+            console.debug('closed port ' + port_ident + 
+                '; ' + Object.keys(connected_ports).length + ' remaining');
+            return;
+        }
+
+    }, false);
+
+    port.start();
+
+    console.debug('added port ' + port_ident + 
+      '; ' + Object.keys(connected_ports).length + ' total');
+}
+