GUI -- Cleaned up websocket code.
 - isolated new WebSocket() call, so we can mock.

Change-Id: Id1225e2c65732e750b289224e838a326c79f02a4
diff --git a/web/gui/src/main/webapp/app/fw/remote/websocket.js b/web/gui/src/main/webapp/app/fw/remote/websocket.js
index 3229250..6445d36 100644
--- a/web/gui/src/main/webapp/app/fw/remote/websocket.js
+++ b/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -21,32 +21,116 @@
     'use strict';
 
     // injected refs
-    var fs, $log;
+    var $log, $loc, fs, ufs, wsock, vs;
 
     // internal state
-    var ws, sws, sid = 0,
-        handlers = {};
+    var ws = null,              // web socket reference
+        wsUp = false,           // web socket is good to go
+        sid = 0,                // event sequence identifier
+        handlers = {},          // event handler bindings
+        pendingEvents = [],     // events TX'd while socket not up
+        url;                    // web socket URL
 
+
+    // ==========================
+    // === Web socket callbacks
+
+    function handleOpen() {
+        $log.info('Web socket open');
+        $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
+        pendingEvents.forEach(function (ev) {
+            _send(ev);
+        });
+        pendingEvents = [];
+        wsUp = true;
+    }
+
+    // Handles the specified (incoming) message using handler bindings.
+    function handleMessage(msgEvent) {
+        var ev, h;
+
+        try {
+            ev = JSON.parse(msgEvent.data);
+            $log.debug(' *Rx* >> ', ev.event, ev.payload);
+
+            if (h = handlers[ev.event]) {
+                h(ev.payload);
+            } else {
+                $log.warn('Unhandled event:', ev);
+            }
+
+        } catch (e) {
+            $log.error('Message.data is (probably) not valid JSON', msgEvent);
+        }
+    }
+
+    function handleClose() {
+        $log.info('Web socket closed');
+        wsUp = false;
+
+        // FIXME: implement controller failover logic
+
+        // If no controllers left to contact, show the Veil...
+        vs.show([
+            'Oops!',
+            'Web-socket connection to server closed...',
+            'Try refreshing the page.'
+        ]);
+    }
+
+
+    // ==============================
+    // === Private Helper Functions
+
+    function _send(ev) {
+        $log.debug(' *Tx* >> ', ev.event, ev.payload);
+        ws.send(JSON.stringify(ev));
+    }
+
+
+    // ===================
+    // === API Functions
+
+    // Required for unit tests to set to known state
     function resetSid() {
         sid = 0;
     }
 
+    // Currently supported opts:
+    //   wsport: web socket port (other than default 8181)
+    function createWebSocket(opts) {
+        var wsport = (opts && opts.wsport) || null;
+
+        url = ufs.wsUrl('core', wsport);
+
+        $log.debug('Attempting to open websocket to: ' + url);
+        ws = wsock.newWebSocket(url);
+        if (ws) {
+            ws.onopen = handleOpen;
+            ws.onmessage = handleMessage;
+            ws.onclose = handleClose;
+        }
+        // Note: Wsock logs an error if the new WebSocket call fails
+    }
+
     // Binds the specified message handlers.
+    //   keys are the event IDs
+    //   values are the API on which the handler function is a property
     function bindHandlers(handlerMap) {
         var m = d3.map(handlerMap),
             dups = [];
 
-        m.forEach(function (key, value) {
-            var fn = fs.isF(value[key]);
+        m.forEach(function (eventId, api) {
+            var fn = fs.isF(api[eventId]);
             if (!fn) {
-                $log.warn(key + ' binding not a function on ' + value);
+                $log.warn(eventId + ' handler not a function');
                 return;
             }
 
-            if (handlers[key]) {
-                dups.push(key);
+            if (handlers[eventId]) {
+                dups.push(eventId);
             } else {
-                handlers[key] = fn;
+                handlers[eventId] = fn;
             }
         });
         if (dups.length) {
@@ -55,116 +139,46 @@
     }
 
     // Unbinds the specified message handlers.
+    //   Expected that the same map will be used, but we only care about keys
     function unbindHandlers(handlerMap) {
         var m = d3.map(handlerMap);
-        m.forEach(function (key) {
-            delete handlers[key];
+
+        m.forEach(function (eventId) {
+            delete handlers[eventId];
         });
     }
 
-    // Formulates an event message and sends it via the shared web-socket.
+    // Formulates an event message and sends it via the web-socket.
+    //  If the websocket is not up yet, we store it in a pending list.
     function sendEvent(evType, payload) {
-        var p = payload || {};
-        if (sws) {
-            $log.debug(' *Tx* >> ', evType, payload);
-            sws.send({
+        var ev = {
                 event: evType,
                 sid: ++sid,
-                payload: p
-            });
+                payload: payload || {}
+            };
+
+        if (wsUp) {
+            _send(ev);
         } else {
-            $log.warn('sendEvent: no websocket open:', evType, payload);
+            pendingEvents.push(ev);
         }
     }
 
 
-    // Handles the specified message using handler bindings.
-    function handleMessage(msgEvent) {
-        var ev;
-        try {
-            ev = JSON.parse(msgEvent.data);
-            $log.debug(' *Rx* >> ', ev.event, ev.payload);
-            dispatchToHandler(ev);
-        } catch (e) {
-            $log.error('message is not valid JSON', msgEvent);
-        }
-    }
-
-    // Dispatches the message to the appropriate handler.
-    function dispatchToHandler(event) {
-        var handler = handlers[event.event];
-        if (handler) {
-            handler(event.payload);
-        } else {
-            $log.warn('unhandled event:', event);
-        }
-    }
-
-    function handleOpen() {
-        $log.info('web socket open');
-        // FIXME: implement calling external hooks
-    }
-
-    function handleClose() {
-        $log.info('web socket closed');
-        // FIXME: implement reconnect logic
-    }
-
+    // ============================
+    // ===== Definition of module
     angular.module('onosRemote')
     .factory('WebSocketService',
-            ['$log', '$location', 'UrlFnService', 'FnService',
+        ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
+            'VeilService',
 
-        function (_$log_, $loc, ufs, _fs_) {
-            fs = _fs_;
+        function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
             $log = _$log_;
-
-            // Creates a web socket for the given path, returning a "handle".
-            // opts contains the event handler callbacks, etc.
-            function createWebSocket(path, opts) {
-                var o = opts || {},
-                    wsport = opts && opts.wsport,
-                    fullUrl = ufs.wsUrl(path, wsport),
-                    api = {
-                        meta: { path: fullUrl, ws: null },
-                        send: send,
-                        close: close
-                    };
-
-                try {
-                    ws = new WebSocket(fullUrl);
-                    api.meta.ws = ws;
-                } catch (e) {
-                }
-
-                $log.debug('Attempting to open websocket to: ' + fullUrl);
-
-                if (ws) {
-                    ws.onopen = handleOpen;
-                    ws.onmessage = handleMessage;
-                    ws.onclose = handleClose;
-                }
-
-                // Sends a formulated event message via the backing web-socket.
-                function send(ev) {
-                    if (ev && ws) {
-                        ws.send(JSON.stringify(ev));
-                    } else if (!ws) {
-                        $log.warn('ws.send() no web socket open!', fullUrl, ev);
-                    }
-                }
-
-                // Closes the backing web-socket.
-                function close() {
-                    if (ws) {
-                        ws.close();
-                        ws = null;
-                        api.meta.ws = null;
-                    }
-                }
-
-                sws = api; // Make the shared web-socket accessible
-                return api;
-            }
+            $loc = _$loc_;
+            fs = _fs_;
+            ufs = _ufs_;
+            wsock = _wsock_;
+            vs = _vs_;
 
             return {
                 resetSid: resetSid,
@@ -173,6 +187,7 @@
                 unbindHandlers: unbindHandlers,
                 sendEvent: sendEvent
             };
-    }]);
+        }
+    ]);
 
 }());
diff --git a/web/gui/src/main/webapp/app/fw/remote/wsock.js b/web/gui/src/main/webapp/app/fw/remote/wsock.js
new file mode 100644
index 0000000..0c58a79
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/remote/wsock.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Remote -- Web Socket Wrapper Service
+
+ This service provided specifically so that it can be mocked in unit tests.
+ */
+(function () {
+    'use strict';
+
+    angular.module('onosRemote')
+        .factory('WSock', ['$log', function ($log) {
+
+            function newWebSocket(url) {
+                var ws = null;
+                try {
+                    ws = new WebSocket(url);
+                } catch (e) {
+                    $log.error('Unable to create web socket:', e);
+                }
+                return ws;
+            }
+
+            return {
+                newWebSocket: newWebSocket
+            };
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
index 10b6909..e7f29d3 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -27,15 +27,15 @@
     'use strict';
 
     // injected refs
-    var $log, vs, wss, tps, tis, tfs, tss, tts;
+    var $log, wss, tps, tis, tfs, tss, tts;
 
     // internal state
-    var handlers;
+    var handlerMap;
 
     // ==========================
 
-    function createHandlers() {
-        handlers = {
+    function createHandlerMap() {
+        handlerMap = {
             showSummary: tps,
 
             showDetails: tss,
@@ -58,17 +58,14 @@
         };
     }
 
-    var nilApi = {};
-
     angular.module('ovTopo')
     .factory('TopoEventService',
-        ['$log', '$location', 'VeilService', 'WebSocketService',
+        ['$log', '$location', 'WebSocketService',
             'TopoPanelService', 'TopoInstService', 'TopoForceService',
             'TopoSelectService', 'TopoTrafficService',
 
-        function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
+        function (_$log_, $loc, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
             $log = _$log_;
-            vs = _vs_;
             wss = _wss_;
             tps = _tps_;
             tis = _tis_;
@@ -76,18 +73,18 @@
             tss = _tss_;
             tts = _tts_;
 
-            createHandlers();
+            createHandlerMap();
 
-            // FIXME: need to handle async socket open to avoid race
             function start() {
-                wss.bindHandlers(handlers);
+                wss.bindHandlers(handlerMap);
                 wss.sendEvent('topoStart');
+                wss.sendEvent('requestSummary');
                 $log.debug('topo comms started');
             }
 
             function stop() {
-                wss.unbindHandlers();
                 wss.sendEvent('topoStop');
+                wss.unbindHandlers(handlerMap);
                 $log.debug('topo comms stopped');
             }
 
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index cab570a..05154c7 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -53,8 +53,8 @@
     <script src="app/fw/remote/remote.js"></script>
     <script src="app/fw/remote/urlfn.js"></script>
     <script src="app/fw/remote/rest.js"></script>
+    <script src="app/fw/remote/wsock.js"></script>
     <script src="app/fw/remote/websocket.js"></script>
-    <script src="app/fw/remote/wsevent.js"></script>
 
     <script src="app/fw/widget/widget.js"></script>
     <script src="app/fw/widget/table.js"></script>
diff --git a/web/gui/src/main/webapp/onos.js b/web/gui/src/main/webapp/onos.js
index 97f3d06..c1338f6 100644
--- a/web/gui/src/main/webapp/onos.js
+++ b/web/gui/src/main/webapp/onos.js
@@ -84,10 +84,9 @@
                 flash.initFlash();
                 qhs.initQuickHelp();
 
-                // TODO: register handlers for initial messages: instances, settings, etc.
+                // TODO: register handler for user settings, etc.
 
-                // TODO: opts?
-                wss.createWebSocket('core', {
+                wss.createWebSocket({
                     wsport: $location.search().wsport
                 });