'use strict'; (function () { /** * @ngdoc provider * @name $websocketProvider * @module ngWebsocket * @description * HTML5 WebSocket provider for AngularJS */ function $websocketProvider () { var wsp = this; wsp.$$config = { lazy: false, reconnect: true, reconnectInterval: 2000, mock: false, enqueue: false, protocols: null }; wsp.$setup = function (cfg) { cfg = cfg || {}; wsp.$$config = angular.extend({}, wsp.$$config, cfg); return wsp; }; wsp.$get = ['$http', function ($http) { return new $websocketService(wsp.$$config, $http); }]; } /** * @ngdoc service * @name $websocketService * @module ngWebsocketService * @description * HTML5 Websocket service for AngularJS */ function $websocketService (cfg, $http) { var wss = this; wss.$$websocketList = {}; wss.$$config = cfg || {}; wss.$get = function (url) { return wss.$$websocketList[url]; }; wss.$new = function (cfg) { cfg = cfg || {}; // Url or url + protocols initialization if (typeof cfg === 'string') { cfg = {url: cfg}; // url + protocols if (arguments.length > 1) { if (typeof arguments[1] === 'string' && arguments[1].length > 0) cfg.protocols = [arguments[1]]; else if (typeof arguments[1] === 'object' && arguments[1].length > 0) cfg.protocols = arguments[1]; } } // If the websocket already exists, return that instance var ws = wss.$get(cfg.url); if (typeof ws === 'undefined') { var wsCfg = angular.extend({}, wss.$$config, cfg); ws = new $websocket(wsCfg, $http); wss.$$websocketList[wsCfg.url] = ws; } return ws; }; } /** * @ngdoc class * @name $websocket * @module ngWebsocket * @description * HTML5 Websocket wrapper class for AngularJS */ function $websocket (cfg, $http) { var me = this; if (typeof cfg === 'undefined' || (typeof cfg === 'object' && typeof cfg.url === 'undefined')) throw new Error('An url must be specified for WebSocket'); me.$$eventMap = {}; me.$$ws = undefined; me.$$reconnectTask = undefined; me.$$reconnectCopy = true; me.$$queue = []; me.$$config = { url: undefined, lazy: false, reconnect: true, reconnectInterval: 2000, enqueue: false, mock: false, protocols: null }; me.$$fireEvent = function () { var args = []; Array.prototype.push.apply(args, arguments); var event = args.shift(), handlers = me.$$eventMap[event]; if (typeof handlers !== 'undefined') { for (var i = 0; i < handlers.length; i++) { if (typeof handlers[i] === 'function') handlers[i].apply(me, args); } } }; me.$$init = function (cfg) { //https://github.com/wilk/ng-websocket/issues/11 //me.$$ws = cfg.mock ? new $$mockWebsocket(cfg.mock, $http) : new WebSocket(cfg.url, cfg.protocols); if (cfg.mock) { me.$$ws = new $$mockWebsocket(cfg.mock, $http); } else if (cfg.protocols) { me.$$ws = new WebSocket(cfg.url, cfg.protocols); } else { me.$$ws = new WebSocket(cfg.url); } me.$$ws.onmessage = function (message) { try { var decoded = JSON.parse(message.data); me.$$fireEvent(decoded.event, decoded.data); me.$$fireEvent('$message', decoded); } catch (err) { me.$$fireEvent('$message', message.data); } }; me.$$ws.onerror = function (error) { me.$$fireEvent('$error', error); }; me.$$ws.onopen = function () { // Clear the reconnect task if exists if (me.$$reconnectTask) { clearInterval(me.$$reconnectTask); delete me.$$reconnectTask; } // Flush the message queue if (me.$$config.enqueue && me.$$queue.length > 0) { while (me.$$queue.length > 0) { if (me.$ready()) me.$$send(me.$$queue.shift()); else break; } } me.$$fireEvent('$open'); }; me.$$ws.onclose = function () { // Activate the reconnect task // PP https://github.com/cdupetit/ng-websocket/commit/bec0d2c56316871758fcd0e33cb55904efb877f1 if (me.$$config.reconnect && !me.$$reconnectTask) { me.$$reconnectTask = setInterval(function () { if (me.$status() === me.$CLOSED) me.$open(); }, me.$$config.reconnectInterval); } me.$$fireEvent('$close'); }; return me; }; me.$CONNECTING = 0; me.$OPEN = 1; me.$CLOSING = 2; me.$CLOSED = 3; // TODO: it doesn't refresh the view (maybe $apply on something?) /*me.$bind = function (event, scope, model) { me.$on(event, function (message) { model = message; scope.$apply(); }); };*/ me.$on = function () { var handlers = []; Array.prototype.push.apply(handlers, arguments); var event = handlers.shift(); if (typeof event !== 'string' || handlers.length === 0) throw new Error('$on accept two parameters at least: a String and a Function or an array of Functions'); me.$$eventMap[event] = me.$$eventMap[event] || []; for (var i = 0; i < handlers.length; i++) { me.$$eventMap[event].push(handlers[i]); } return me; }; me.$un = function (event) { if (typeof event !== 'string') throw new Error('$un needs a String representing an event.'); if (typeof me.$$eventMap[event] !== 'undefined') delete me.$$eventMap[event]; return me; }; me.$$send = function (message) { if (me.$ready()) me.$$ws.send(JSON.stringify(message)); else if (me.$$config.enqueue) me.$$queue.push(message); }; me.$emit = function (event, data) { if (typeof event !== 'string') throw new Error('$emit needs two parameter: a String and a Object or a String'); var message = { event: event, data: data }; me.$$send(message); return me; }; me.$open = function () { me.$$config.reconnect = me.$$reconnectCopy; if (me.$status() !== me.$OPEN) me.$$init(me.$$config); return me; }; me.$close = function () { if (me.$status() !== me.$CLOSED) me.$$ws.close(); if (me.$$reconnectTask) { clearInterval(me.$$reconnectTask); delete me.$$reconnectTask; } me.$$config.reconnect = false; return me; }; me.$status = function () { if (typeof me.$$ws === 'undefined') return me.$CLOSED; else return me.$$ws.readyState; }; me.$ready = function () { return me.$status() === me.$OPEN; }; me.$mockup = function () { return me.$$config.mock; }; // setup me.$$config = angular.extend({}, me.$$config, cfg); me.$$reconnectCopy = me.$$config.reconnect; if (!me.$$config.lazy) me.$$init(me.$$config); return me; } function $$mockWebsocket (cfg, $http) { cfg = cfg || {}; var me = this, openTimeout = cfg.openTimeout || 500, closeTimeout = cfg.closeTimeout || 1000, messageInterval = cfg.messageInterval || 2000, fixtures = cfg.fixtures || {}, messageQueue = []; me.CONNECTING = 0; me.OPEN = 1; me.CLOSING = 2; me.CLOSED = 3; me.readyState = me.CONNECTING; me.send = function (message) { if (me.readyState === me.OPEN) { messageQueue.push(message); return me; } else throw new Error('WebSocket is already in CLOSING or CLOSED state.'); }; me.close = function () { if (me.readyState === me.OPEN) { me.readyState = me.CLOSING; setTimeout(function () { me.readyState = me.CLOSED; me.onclose(); }, closeTimeout); } return me; }; me.onmessage = function () {}; me.onerror = function () {}; me.onopen = function () {}; me.onclose = function () {}; setInterval(function () { if (messageQueue.length > 0) { var message = messageQueue.shift(), msgObj = JSON.parse(message); switch (msgObj.event) { case '$close': me.close(); break; default: // Check for a custom response if (typeof fixtures[msgObj.event] !== 'undefined') { msgObj.data = fixtures[msgObj.event].data || msgObj.data; msgObj.event = fixtures[msgObj.event].event || msgObj.event; } message = JSON.stringify(msgObj); me.onmessage({ data: message }); } } }, messageInterval); var start = function (fixs) { fixs = fixs || {}; fixs = fixs instanceof Error ? {} : fixs; fixtures = fixs; setTimeout(function () { me.readyState = me.OPEN; me.onopen(); }, openTimeout); }; // Get fixtures from a server or a file if it's a string if (typeof fixtures === 'string') { $http.get(fixtures) .success(start) .error(start); } else start(fixtures); return me; } /** * @ngdoc module * @name $websocket * @module ngWebsocket * @description * HTML5 WebSocket module for AngularJS */ angular .module('ngWebsocket', []) .provider('$websocket', $websocketProvider); })();