API Docs for: 0.2.2
Show:

File: src\R.SimpleUplinkServer.js

module.exports = function(R) {
    var io = require("socket.io");
    var _ = require("lodash");
    var assert = require("assert");
    var co = require("co");
    var EventEmitter = require("events").EventEmitter;
    var bodyParser = require("body-parser");

    /**
    * <p> SimpleUplinkServer represents an uplink-server that will be able to store data via an other server.<br />
    * There also will be able to notify each client who suscribes to a data when an update will occurs thanks to socket </p>
    * <p> SimpleUplinkServer will be requested by GET or POST via R.Uplink server-side and client-side
    * @class R.SimpleUplinkServer
    */
    var SimpleUplinkServer = {
        /**
        * <p> Initializes the SimpleUplinkServer according to the specifications provided </p>
        * @method createApp
        * @param {object} specs All the specifications of the SimpleUplinkServer
        * @return {SimpleUplinkServerInstance} SimpleUplinkServerInstance The instance of the created SimpleUplinkServer
        */
        createServer: function createServer(specs) {
            R.Debug.dev(function() {
                assert(specs.store && _.isArray(specs.store), "R.SimpleUplinkServer.createServer(...).specs.store: expecting Array.");
                assert(specs.events && _.isArray(specs.events), "R.SimpleUplinkServer.createServer(...).specs.events: expecting Array.");
                assert(specs.actions && _.isPlainObject(specs.actions), "R.SimpleUplinkServer.createServer(...).specs.actions: expecting Object.");
                assert(specs.sessionCreated && _.isFunction(specs.sessionCreated), "R.SimpleUplinkServer.createServer(...).specs.sessionCreated: expecting Function.");
                assert(specs.sessionDestroyed && _.isFunction(specs.sessionDestroyed), "R.SimpleUplinkServer.createServer(...).specs.sessionDestroyed: expecting Function.");
                assert(specs.sessionTimeout && _.isNumber(specs.sessionTimeout), "R.SimpleUplinkServer.createServer(...).specs.sessionTimeout: expecting Number.");
            });
            var SimpleUplinkServerInstance = function SimpleUplinkServerInstance() {
                SimpleUplinkServer.SimpleUplinkServerInstance.call(this);
                this._specs = specs;
                this._pid = R.guid("SimpleUplinkServer");
            };
            _.extend(SimpleUplinkServerInstance.prototype, SimpleUplinkServer.SimpleUplinkServerInstance.prototype, specs);
            return SimpleUplinkServerInstance;
        },
        /**
        * <p> Setting up necessary methods for the SimpleUplinkServer </p>
        * @method SimpleUplinkServerInstance
        */
        SimpleUplinkServerInstance: function SimpleUplinkServerInstance() {
            this._store = {};
            this._hashes = {};
            this._storeRouter = new R.Router();
            this._storeRouter.def(_.constant({
                err: "Unknown store key",
            }));
            this._storeEvents = new EventEmitter();
            this._eventsRouter = new R.Router();
            this._eventsRouter.def(_.constant({
                err: "Unknown event name",
            }));
            this._eventsEvents = new EventEmitter();
            this._actionsRouter = new R.Router();
            this._actionsRouter.def(_.constant({
                err: "Unknown action",
            }));
            this._sessions = {};
            this._sessionsEvents = new EventEmitter();
            this._connections = {};

            this._linkSession = R.scope(this._linkSession, this);
            this._unlinkSession = R.scope(this._unlinkSession, this);
        },
        _SimpleUplinkServerInstanceProtoProps: /** @lends R.SimpleUplinkServer.SimpleUplinkServerInstance.prototype */{
            _specs: null,
            _pid: null,
            _prefix: null,
            _app: null,
            _io: null,
            _store: null,
            _storeEvents: null,
            _storeRouter: null,
            _eventsRouter: null,
            _eventsEvents: null,
            _actionsRouter: null,
            _sessions: null,
            _sessionsEvents: null,
            _connections: null,
            bootstrap: null,
            sessionCreated: null,
            sessionDestroyed: null,
            sessionTimeout: null,
            /**
            * <p>Saves data in store.
            * Called by another server that will provide data for each updated data </p>
            * @method setStore
            * @param {string} key The specified key to set
            * @param {string} val The value to save
            * @return {function} 
            */
            setStore: function setStore(key, val) {
                return R.scope(function(fn) {
                    try {
                        var previousVal = this._store[key] || {};
                        var previousHash = this._hashes[key] || R.hash(JSON.stringify(previousVal));
                        var diff = R.diff(previousVal, val);
                        var hash = R.hash(JSON.stringify(val));
                        this._store[key] = val;
                        this._hashes[key] = hash;
                        this._storeEvents.emit("set:" + key, {
                            k: key,
                            d: diff,
                            h: previousHash,
                        });
                    }
                    catch(err) {
                        return fn(R.Debug.extendError(err, "R.SimpleUplinkServer.setStore('" + key + "', '" + val + "')"));
                    }
                    _.defer(function() {
                        fn(null, val);
                    });
                }, this);
            },

            /**
            * <p> Provides data from store. <br />
            * Called when the fetching data occurs. <br />
            * Requested by GET from R.Store server-side or client-side</p>
            * @method getStore
            * @param {string} key The specified key to set
            * @return {function} 
            */
            getStore: function getStore(key) {
                return R.scope(function(fn) {
                    var val;
                    try {
                        R.Debug.dev(R.scope(function() {
                            if(!_.has(this._store, key)) {
                                console.warn("R.SimpleUplinkServer(...).getStore: no such key (" + key + ")");
                            }
                        }, this));
                        val = this._store[key];
                    }
                    catch(err) {
                        return fn(R.Debug.extendError(err, "R.SimpleUplinkServer.getStore('" + key + "')"));
                    }
                    _.defer(function() {
                        fn(null, val);
                    });
                }, this);
            },
            /**
            * @method emitEvent
            * @param {string} eventName
            * @param {object} params
            */
            emitEvent: function emitEvent(eventName, params) {
                this._eventsEvents.emit("emit:" + eventName, params);
            },
            /**
            * @method emitDebug
            * @param {string} guid
            * @param {object} params
            */
            emitDebug: function emitDebug(guid, params) {
                R.Debug.dev(R.scope(function() {
                    if(this._sessions[guid]) {
                        this._sessions[guid].emit("debug", params);
                    }
                }, this));
            },
            /**
            * @method emitLog
            * @param {string} guid
            * @param {object} params
            */
            emitLog: function emitLog(guid, params) {
                if(this._sessions[guid]) {
                    this._sessions[guid].emit("log", params);
                }
            },
            /**
            * @method emitWarn
            * @param {string} guid
            * @param {object} params
            */
            emitWarn: function emitLog(guid, params) {
                if(this._sessions[guid]) {
                    this._sessions[guid].emit("warn", params);
                }
            },
            /**
            * @method emitError
            * @param {string} guid
            * @param {object} params
            */            
            emitError: function emitLog(guid, params) {
                if(this._sessions[guid]) {
                    this._sessions[guid].emit("err", params);
                }
            },
            _extractOriginalPath: function _extractOriginalPath() {
                return arguments[arguments.length - 1];
            },
            _bindStoreRoute: function _bindStoreRoute(route) {
                this._storeRouter.route(route, this._extractOriginalPath);
            },
            _bindEventsRoute: function _bindEventsRoute(route) {
                this._eventsRouter.route(route, this._extractOriginalPath);
            },
            _bindActionsRoute: function _bindActionsRoute(handler, route) {
                this._actionsRouter.route(route, _.constant(R.scope(handler, this)));
            },
            /** 
            * <p> Setting up UplinkServer. <br />
            * - create the socket connection <br />
            * - init get and post app in order to provide data via R.Uplink.fetch</p>
            * @method installHandlers
            * @param {object} app The specified App
            * @param {string} prefix The prefix string that will be requested. Tipically "/uplink"
            */
            installHandlers: function* installHandlers(app, prefix) {
                assert(this._app === null, "R.SimpleUplinkServer.SimpleUplinkServerInstance.installHandlers(...): app already mounted.");
                this._app = app;
                this._prefix = prefix || "/uplink/";
                var server = require("http").Server(app);
                this._io = io(server).of(prefix);
                this._app.get(this._prefix + "*", R.scope(this._handleHttpGet, this));
                this._app.post(this._prefix + "*", bodyParser.json(), R.scope(this._handleHttpPost, this));
                this._io.on("connection", R.scope(this._handleSocketConnection, this));
                this._handleSocketDisconnection = R.scope(this._handleSocketDisconnection, this);
                this._sessionsEvents.addListener("expire", R.scope(this._handleSessionExpire, this));
                _.each(this._specs.store, R.scope(this._bindStoreRoute, this));
                _.each(this._specs.events, R.scope(this._bindEventsRoute, this));
                _.each(this._specs.actions, R.scope(this._bindActionsRoute, this));
                this.bootstrap = R.scope(this._specs.bootstrap, this);
                yield this.bootstrap();
                return server;
            },
            /**
            * <p>Return the saved data from store</p>
            * <p>Requested from R.Store server-side or client-side</p>
            * @method _handleHttpGet
            * @param {object} req The classical request
            * @param {object} res The response to send
            * @param {object} next
            * @return {string} val The computed json value
            */
            _handleHttpGet: function _handleHttpGet(req, res, next) {
                co(function*() {
                    var path = req.path.slice(this._prefix.length - 1); // keep the leading slash
                    var key = this._storeRouter.match(path);
                    R.Debug.dev(function() {
                        console.warn("<<< fetch", path);
                    });
                    return yield this.getStore(key);
                }).call(this, function(err, val) {
                    if(err) {
                        if(R.Debug.isDev()) {
                            return res.status(500).json({ err: err.toString(), stack: err.stack });
                        }
                        else {
                            return res.status(500).json({ err: err.toString() });
                        }
                    }
                    else {
                        return res.status(200).json(val);
                    }
                });
            },
            /**
            * @method _handleHttpPost
            * @param {object} req The classical request
            * @param {object} res The response to send
            * @return {string} str
            */
            _handleHttpPost: function _handleHttpPost(req, res) {
                co(function*() {
                    var path = req.path.slice(this._prefix.length - 1); // keep the leading slash
                    var handler = this._actionsRouter.match(path);
                    assert(_.isObject(req.body), "body: expecting Object.");
                    assert(req.body.guid && _.isString(req.body.guid), "guid: expecting String.");
                    assert(req.body.params && _.isPlainObject(req.body.params), "params: expecting Object.");
                    if(!_.has(this._sessions, req.body.guid)) {
                        this._sessions[guid] = new R.SimpleUplinkServer.Session(guid, this._storeEvents, this._eventsEvents, this._sessionsEvents, this.sessionTimeout);
                        yield this.sessionCreated(guid);
                    }
                    var params = _.extend({}, { guid: req.body.guid }, req.body.params);
                    R.Debug.dev(function() {
                        console.warn("<<< action", path, params);
                    });
                    return yield handler(params);
                }).call(this, function(err, val) {
                    if(err) {
                        if(R.Debug.isDev()) {
                            return res.status(500).json({ err: err.toString(), stack: err.stack });
                        }
                        else {
                            return res.status(500).json({ err: err.toString() });
                        }
                    }
                    else {
                        res.status(200).json(val);
                    }
                });
            },
            /** 
            * <p> Create a R.SimpleUplinkServer.Connection in order to set up handler items. <br />
            * Triggered when a socket connection is established </p>
            * @method _handleSocketConnection
            * @param {Object} socket The socket used in the connection
            */
            _handleSocketConnection: function _handleSocketConnection(socket) {
                var connection = new R.SimpleUplinkServer.Connection(this._pid, socket, this._handleSocketDisconnection, this._linkSession, this._unlinkSession);
                this._connections[connection.uniqueId] = connection;
            },

            /** 
            * <p> Destroy a R.SimpleUplinkServer.Connection. <br />
            * Triggered when a socket connection is closed </p>
            * @method _handleSocketDisconnection
            * @param {string} uniqueId The unique Id of the connection
            */
            _handleSocketDisconnection: function _handleSocketDisconnection(uniqueId) {
                var guid = this._connections[uniqueId].guid;
                if(guid && this._sessions[guid]) {
                    this._sessions[guid].detachConnection();
                }
                delete this._connections[uniqueId];
            },

            /** 
            * <p>Link a Session in order to set up subscribing and unsubscribing methods uplink-server-side</p>
            * @method _linkSession
            * @param {SimpleUplinkServer.Connection} connection The created connection
            * @param {string} guid Unique string GUID
            * @return {object} the object that contains methods subscriptions/unsubscriptions
            */
            _linkSession: function* _linkSession(connection, guid) {
                if(!this._sessions[guid]) {
                    this._sessions[guid] = new R.SimpleUplinkServer.Session(guid, this._storeEvents, this._eventsEvents, this._sessionsEvents, this.sessionTimeout);
                    yield this.sessionCreated(guid);
                }
                return this._sessions[guid].attachConnection(connection);
            },

            /** 
            * <p>Unlink a Session</p>
            * @method _unlinkSession
            * @param {SimpleUplinkServer.Connection} connection 
            * @param {string} guid Unique string GUID
            * @return {Function} fn
            */
            _unlinkSession: function _unlinkSession(connection, guid) {
                return R.scope(function(fn) {
                    try {
                        if(this._sessions[guid]) {
                            this._sessions[guid].terminate();
                        }
                    }
                    catch(err) {
                        return fn(R.Debug.extendError("R.SimpleUplinkServerInstance._unlinkSession(...)"));
                    }
                    return fn(null);
                }, this);
            },
            /** 
            * @method _handleSessionExpire
            * @param {string} guid Unique string GUID
            */
            _handleSessionExpire: function _handleSessionExpire(guid) {
                R.Debug.dev(R.scope(function() {
                    assert(_.has(this._sessions, guid), "R.SimpleUplinkServer._handleSessionExpire(...): no such session.");
                }, this));
                delete this._sessions[guid];
                co(function*() {
                    yield this.sessionDestroyed(guid);
                }).call(this, R.Debug.rethrow("R.SimpleUplinkServer._handleSessionExpire(...)"));
            },
        },
        /** 
        * <p>Setting up a connection in order to initialies methods and to provides specifics listeners on the socket</p>
        * @method Connection
        * @param {object} pid 
        * @param {object} socket
        * @param {object} handleSocketDisconnection
        * @param {object} linkSession 
        * @param {object} unlinkSession
        */
        Connection: function Connection(pid, socket, handleSocketDisconnection, linkSession, unlinkSession) {
            this._pid = pid;
            this.uniqueId = _.uniqueId("R.SimpleUplinkServer.Connection");
            this._socket = socket;
            this._handleSocketDisconnection = handleSocketDisconnection;
            this._linkSession = linkSession;
            this._unlinkSession = unlinkSession;
            this._bindHandlers();
        },
        _ConnectionProtoProps: /** @lends R.SimpleUplinkServer.Connection.prototype */{
            _socket: null,
            _pid: null,
            uniqueId: null,
            guid: null,
            _handleSocketDisconnection: null,
            _linkSession: null,
            _unlinkSession: null,
            _subscribeTo: null,
            _unsubscribeFrom: null,
            _listenTo: null,
            _unlistenFrom: null,
            _disconnected: null,
            /** 
            * <p>Setting up the specifics listeners for the socket</p>
            * @method _bindHandlers
            */
            _bindHandlers: function _bindHandlers() {
                this._socket.on("handshake", R.scope(this._handleHandshake, this));
                this._socket.on("subscribeTo", R.scope(this._handleSubscribeTo, this));
                this._socket.on("unsubscribeFrom", R.scope(this._handleUnsubscribeFrom, this));
                this._socket.on("listenTo", R.scope(this._handleListenTo, this));
                this._socket.on("unlistenFrom", R.scope(this._handleUnlistenFrom, this));
                this._socket.on("disconnect", R.scope(this._handleDisconnect, this));
                this._socket.on("unhandshake", R.scope(this._handleUnHandshake, this));
            },
            /**
            * <p> Simply emit a specific action on the socket </p>
            * @method emit
            * @param {string} name The name of the action to send
            * @param {object} params The params 
            */
            emit: function emit(name, params) {
                R.Debug.dev(function() {
                    console.warn("[C] >>> " + name, params);
                });
                this._socket.emit(name, params);
            },
            /**
            * <p> Triggered by the recently connected client. <br />
            * Combines methods of subscriptions that will be triggered by the client via socket listening</p>
            * @method _handleHandshake
            * @param {String} params Contains the unique string GUID
            */
            _handleHandshake: function _handleHandshake(params) {
                if(!_.has(params, "guid") || !_.isString(params.guid)) {
                    this.emit("err", { err: "handshake.params.guid: expected String."});
                }
                else if(this.guid) {
                    this.emit("err", { err: "handshake: session already linked."});
                }
                else {
                    co(function*() {
                        this.guid = params.guid;
                        var s = yield this._linkSession(this, this.guid);
                        this.emit("handshake-ack", {
                            pid: this._pid,
                            recovered: s.recovered,
                        });
                        this._subscribeTo = s.subscribeTo;
                        this._unsubscribeFrom = s.unsubscribeFrom;
                        this._listenTo = s.listenTo;
                        this._unlistenFrom = s.unlistenFrom;
                    }).call(this, R.Debug.rethrow("R.SimpleUplinkServer.Connection._handleHandshake(...)"));
                }
            },
            /**
            * <p> Triggered by the recently disconnected client. <br />
            * Removes methods of subscriptions</p>
            * @method _handleHandshake
            */
            _handleUnHandshake: function _handleUnHandshake() {
                if(!this.guid) {
                    this.emit("err", { err: "unhandshake: no active session."});
                }
                else {
                    co(function*() {
                        this._subscribeTo = null;
                        this._unsubscribeFrom = null;
                        this._listenTo = null;
                        this._unlistenFrom = null;
                        var s = yield this._unlinkSession(this, this.guid);
                        this.emit("unhandshake-ack");
                        this.guid = null;
                    }).call(this, R.Debug.rethrow("R.SimpleUplinkServer.Connection._handleUnHandshake(...)"));
                }
            },
            /** 
            * <p>Maps the triggered event with the _subscribeTo methods </p>
            * @method _handleSubscribeTo
            * @param {object} params Contains the key provided by client
            */
            _handleSubscribeTo: function _handleSubscribeTo(params) {
                if(!_.has(params, "key") || !_.isString(params.key)) {
                    this.emit("err", { err: "subscribeTo.params.key: expected String." });
                }
                else if(!this._subscribeTo) {
                    this.emit("err", { err: "subscribeTo: requires handshake." });
                }
                else {
                    this._subscribeTo(params.key);
                }
            },
            /** 
            * <p>Maps the triggered event with the _unsubscribeFrom methods</p>
            * @method _handleUnsubscribeFrom
            * @param {object} params Contains the key provided by client
            */
            _handleUnsubscribeFrom: function _handleUnsubscribeFrom(params) {
                if(!_.has(params, "key") || !_.isString(params.key)) {
                    this.emit("err", { err: "unsubscribeFrom.params.key: expected String." });
                }
                else if(!this._unsubscribeFrom) {
                    this.emit("err", { err: "unsubscribeFrom: requires handshake." });
                }
                else {
                    this._unsubscribeFrom(params.key);
                }
            },
            /** 
            * <p>Maps the triggered event with the listenTo methods</p>
            * @method _handleListenTo
            * @param {object} params Contains the eventName provided by client
            */
            _handleListenTo: function _handleListenTo(params) {
                if(!_.has(params, "eventName") || !_.isString(params.eventName)) {
                    this.emit("err", { err: "listenTo.params.eventName: expected String." });
                }
                else if(!this._listenTo) {
                    this.emit("err", { err: "listenTo: requires handshake." });
                }
                else {
                    this.listenTo(params.eventName);
                }
            },
            /** 
            * <p>Maps the triggered event with the unlistenFrom methods</p>
            * @method _handleUnlistenFrom
            * @param {object} params Contains the eventName provided by client
            */
            _handleUnlistenFrom: function _handleUnlistenFrom(params) {
                if(!_.has(params, "eventName") || !_.isString(params.eventName)) {
                    this.emit("err", { err: "unlistenFrom.params.eventName: expected String." });
                }
                else if(!this.unlistenFrom) {
                    this._emit("err", { err: "unlistenFrom: requires handshake." });
                }
                else {
                    this.unlistenFrom(params.eventName);
                }
            },
             /** 
            * <p>Triggered by the recently disconnected client.</p>
            * @method _handleDisconnect
            */
            _handleDisconnect: function _handleDisconnect() {
                this._handleSocketDisconnection(this.uniqueId, false);
            },
        },
        /** 
        * <p>Setting up a session</p>
        * @method Session
        * @param {object} pid 
        * @param {object} storeEvents
        * @param {object} eventsEvents
        * @param {object} sessionsEvents 
        * @param {object} timeout
        */
        Session: function Session(guid, storeEvents, eventsEvents, sessionsEvents, timeout) {
            this._guid = guid;
            this._storeEvents = storeEvents;
            this._eventsEvents = eventsEvents;
            this._sessionsEvents = sessionsEvents;
            this._messageQueue = [];
            this._timeoutDuration = timeout;
            this._expire = R.scope(this._expire, this);
            this._expireTimeout = setTimeout(this._expire, this._timeoutDuration);
            this._subscriptions = {};
            this._listeners = {};
        },
        _SessionProtoProps: /** @lends R.SimpleUplinkServer.Session.prototype */{
            _guid: null,
            _connection: null,
            _subscriptions: null,
            _listeners: null,
            _storeEvents: null,
            _eventsEvents: null,
            _sessionsEvents: null,
            _messageQueue: null,
            _expireTimeout: null,
            _timeoutDuration: null,
            /**
            * <p>Bind the subscribing and unsubscribing methods when a connection is established <br />
            * Methods that trigger on client issues (like emit("subscribeTo"), emit("unsubscribeFrom"))</p>
            * @method attachConnection
            * @param {SimpleUplinkServer.Connection} connection the current created connection
            * @return {object} the binded object with methods
            */
            attachConnection: function attachConnection(connection) {
                var recovered = (this._connection !== null);
                this.detachConnection();
                this._connection = connection;
                _.each(this._messageQueue, function(m) {
                    connection.emit(m.name, m.params);
                });
                this._messageQueue = null;
                clearTimeout(this._expireTimeout);
                return {
                    recovered: recovered,
                    subscribeTo: R.scope(this.subscribeTo, this),
                    unsubscribeFrom: R.scope(this.unsubscribeFrom, this),
                    listenTo: R.scope(this.listenTo, this),
                    unlistenFrom: R.scope(this.unlistenFrom, this),
                };
            },
            /**
            * <p>Remove the previously added connection, and clean the message queue </p>
            * @method detachConnection
            */
            detachConnection: function detachConnection() {
                if(this._connection === null) {
                    return;
                }
                else {
                    this._connection = null;
                    this._messageQueue = [];
                    this._expireTimeout = setTimeout(this._expire, this._timeoutDuration);
                }
            },
            /**
            * @method terminate
            */
            terminate: function terminate() {
                this._expire();
            },
            /** 
            * <p>Method invoked by client via socket emit <br />
            * Store the _signalUpdate method in subscription <br />
            * Add a listener that will call _signalUpdate when triggered </p>
            * @method subscribeTo
            * @param {string} key The key to subscribe
            */
            subscribeTo: function subscribeTo(key) {
                R.Debug.dev(R.scope(function() {
                    assert(!_.has(this._subscriptions, key), "R.SimpleUplinkServer.Session.subscribeTo(...): already subscribed.");
                }, this));
                this._subscriptions[key] = this._signalUpdate();
                this._storeEvents.addListener("set:" + key, this._subscriptions[key]);
            },

            /** 
            * <p>Method invoked by client via socket emit <br />
            * Remove a listener according to the key</p>
            * @method subscribeTo
            * @param {string} key The key to unsubscribe
            */
            unsubscribeFrom: function unsubscribeFrom(key) {
                R.Debug.dev(R.scope(function() {
                    assert(_.has(this._subscriptions, key), "R.SimpleUplinkServer.Session.unsubscribeFrom(...): not subscribed.");
                }, this));
                this._storeEvents.removeListener("set:" + key, this._subscriptions[key]);
                delete this._subscriptions[key];
            },
            /**
            * <p> Simply emit a specific action on the socket </p>
            * @method _emit
            * @param {string} name The name of the action to send
            * @param {object} params The params 
            */
            _emit: function _emit(name, params) {
                R.Debug.dev(function() {
                    console.warn("[S] >>> " + name, params);
                });
                if(this._connection !== null) {
                    this._connection.emit(name, params);
                }
                else {
                    this._messageQueue.push({
                        name: name,
                        params: params,
                    });
                }
            },
            /** <p>Push an update action on the socket. <br />
            * The client is listening on the action "update" socket </p>
            * @method _signalUpdate
            */
            _signalUpdate: function _signalUpdate() {
                return R.scope(function(patch) {
                    this._emit("update", patch);
                }, this);
            },
            /** <p>Push an event action on the socket. <br />
            * The client is listening on the action "event" socket </p>
            * @method _signalEvent
            */
            _signalEvent: function _signalEvent(eventName) {
                return R.scope(function(params) {
                    this._emit("event", { eventName: eventName, params: params });
                }, this);
            },
            /**
            * @method _expire
            */
            _expire: function _expire() {
                _.each(_.keys(this._subscriptions), R.scope(this.unsubscribeFrom, this));
                _.each(_.keys(this._listeners), R.scope(this.unlistenFrom, this));
                this._sessionsEvents.emit("expire", this._guid);
            },
            /**
            * <p> Create a listener for the events </p>
            * @method listenTo
            * @param {string} eventName The name of the event that will be registered
            */
            listenTo: function listenTo(eventName) {
                R.Debug.dev(R.scope(function() {
                    assert(!_.has(this._listeners, key), "R.SimpleUplinkServer.Session.listenTo(...): already listening.");
                }, this));
                this._listeners[eventName] = this._signalEvent(eventName);
                this._eventsEvents.addListener("emit:" + eventName, this._listeners[eventName]);
            },
            /**
            * <p> Remove a listener from the events </p>
            * @method unlistenFrom
            * @param {string} eventName The name of the event that will be unregistered
            */
            unlistenFrom: function unlistenFrom(eventName) {
                R.Debug.dev(R.scope(function() {
                    assert(_.has(this._listeners, eventName), "R.SimpleUplinkServer.Session.unlistenFrom(...): not listening.");
                }, this));
                this._eventsEvents.removeListener("emit:" + eventName, this._listeners[eventName]);
                delete this._listeners[eventName];
            },
        },
    };

    _.extend(SimpleUplinkServer.SimpleUplinkServerInstance.prototype, SimpleUplinkServer._SimpleUplinkServerInstanceProtoProps);
    _.extend(SimpleUplinkServer.Connection.prototype, SimpleUplinkServer._ConnectionProtoProps);
    _.extend(SimpleUplinkServer.Session.prototype, SimpleUplinkServer._SessionProtoProps);

    return SimpleUplinkServer;
};