websocket.js 5.33 KB
/*
 * 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 Service
 */
(function () {
    'use strict';

    // injected refs
    var $log, $loc, fs, ufs, wsock, vs;

    // internal state
    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 (eventId, api) {
            var fn = fs.isF(api[eventId]);
            if (!fn) {
                $log.warn(eventId + ' handler not a function');
                return;
            }

            if (handlers[eventId]) {
                dups.push(eventId);
            } else {
                handlers[eventId] = fn;
            }
        });
        if (dups.length) {
            $log.warn('duplicate bindings ignored:', dups);
        }
    }

    // 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 (eventId) {
            delete handlers[eventId];
        });
    }

    // 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 ev = {
                event: evType,
                sid: ++sid,
                payload: payload || {}
            };

        if (wsUp) {
            _send(ev);
        } else {
            pendingEvents.push(ev);
        }
    }


    // ============================
    // ===== Definition of module
    angular.module('onosRemote')
    .factory('WebSocketService',
        ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
            'VeilService',

        function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
            $log = _$log_;
            $loc = _$loc_;
            fs = _fs_;
            ufs = _ufs_;
            wsock = _wsock_;
            vs = _vs_;

            return {
                resetSid: resetSid,
                createWebSocket: createWebSocket,
                bindHandlers: bindHandlers,
                unbindHandlers: unbindHandlers,
                sendEvent: sendEvent
            };
        }
    ]);

}());