Source: easyrtc_public_obj.js

/**
 * Public interface for interacting with EasyRTC. Contains the public object returned by the EasyRTC listen() function.
 *
 * @module      easyrtc_public_obj
 * @author      Priologic Software, info@easyrtc.com
 * @copyright   Copyright 2013 Priologic Software. All rights reserved.
 * @license     BSD v2, see LICENSE file in module root folder.
 */

var events          = require("events");
var async           = require("async");
var _               = require("underscore");                // General utility functions external module
var g               = require("./general_util");            // General utility functions local module

var e               = require("./easyrtc_private_obj");     // EasyRTC private object
var eventListener   = require("./easyrtc_default_event_listeners"); // EasyRTC default event listeners
var eu              = require("./easyrtc_util");            // EasyRTC utility functions

/**
 * The public object which is returned by the EasyRTC listen() function. Contains all public methods for interacting with EasyRTC server.
 *
 * @class
 */
var pub = module.exports;


/**
 * Alias for Socket.io server object. Set during Listen().
 *
 * @member  {Object}    pub.socketServer
 * @example             <caption>Dump of all Socket.IO clients to server console</caption>
 * console.log(pub.socketServer.connected);
 */
pub.socketServer = null;


/**
 * Alias for Express app object. Set during Listen()
 *
 * @member  {Object}    pub.httpApp
 */
pub.httpApp = null;


/**
 * Sends an array of all application names to a callback.
 *
 * @param   {function(Error, Array.<string>)} callback Callback with error and array containing all application names.
 */
pub.getAppNames = function (callback) {
    var appNames = new Array();
    for (var key in e.app) {
        appNames.push(key);
    };
    callback(null, appNames);
};


/**
 * Gets app object for application which has an authenticated client with a given easyrtcid
 *
 * @param       {String} easyrtcid      Unique identifier for an EasyRTC connection.
 * @param       {function(Error, Object)} callback Callback with error and application object
 */
pub.getAppWithEasyrtcid = function(easyrtcid, callback) {
    for(var key in e.app) {
        if (e.app[key].connection[easyrtcid] && e.app[key].connection[easyrtcid].isAuthenticated) {
            pub.app(key, callback);
            return;
        }
    }
    pub.util.logWarning("Can not find connection ["+ easyrtcid +"]");
    callback(new pub.util.ConnectionWarning("Can not find connection ["+ easyrtcid +"]"));
};


/**
 * Gets connection object for connection which has an authenticated client with a given easyrtcid
 *
 * @param       {string} easyrtcid      EasyRTC unique identifier for a socket connection.
 * @param       {function(Error, Object)} callback Callback with error and connection object
 */
pub.getConnectionWithEasyrtcid = function(easyrtcid, callback) {
    for(var key in e.app) {
        if (e.app[key].connection[easyrtcid] && e.app[key].connection[easyrtcid].isAuthenticated) {
            pub.app(key, function(err,appObj) {
                if (err) {
                    callback(err);
                    return;
                }
                appObj.connection(easyrtcid, callback);
            });
            return;
        }
    }
    pub.util.logWarning("Can not find connection ["+ easyrtcid +"]");
    callback(new pub.util.ConnectionWarning("Can not find connection ["+ easyrtcid +"]"));
};


/**
 * Gets individual option value. The option value returned is for the server level.
 * 
 * Note that some options can be set at the application or room level. If an option has not been set at the room level, it will check to see if it has been set at the application level, if not it will revert to the server level.
 *
 * @param       {String}    option      Option name
 * @return      {*}                     Option value (can be any JSONable type)
 */
pub.getOption = function(optionName) {
    return e.option[optionName];
};


/**
 * Gets EasyRTC Version. The format is in a major.minor.patch format with an optional letter following denoting alpha or beta status. The version is retrieved from the package.json file located in the EasyRTC project root folder.
 *
 * @return      {string}                EasyRTC Version
 */
pub.getVersion = function() {
    return e.version;
};


/**
 * Returns the EasyRTC private object containing the current state. This should only be used for debugging purposes.
 *
 * @private
 * @return      {Object}                EasyRTC private object
 */
pub._getPrivateObj = function(){
    return e;
};


/**
 * Sets individual option. The option value set is for the server level.
 * 
 * Note that some options can be set at the application or room level. If an option has not been set at the room level, it will check to see if it has been set at the application level, if not it will revert to the server level.
 *
 * @param       {Object} option         Option name
 * @param       {Object} value          Option value
 * @return      {Boolean}               true on success, false on failure
 */
pub.setOption = function(optionName, optionValue) {
    // Can only set options which currently exist
    if (typeof e.option[optionName] == "undefined") {
        pub.util.logError("Error setting option. Unrecognised option name '" + optionName + "'.");
        return false;
     }

    e.option[optionName] = pub.util.deepCopy(optionValue);
    return true;
};


/**
 * EasyRTC Event handling object which contain most methods for interacting with EasyRTC events. For convenience, this class has also been attached to the application, connection, session, and room classes.
 * @class
 */
pub.events = {};


/**
 * EasyRTC EventEmmitter.
 * 
 * @private
 */
pub.events._eventListener = new events.EventEmitter();


/**
 * Expose event listener's emit function.
 * 
 * @param       {string} eventName      EasyRTC event name.
 * @param       {...*} eventParam       The event parameters
 */
pub.events.emit = pub.events._eventListener.emit.bind(pub.events._eventListener);


/**
 * Runs the default EasyRTC listener for a given event.
 * 
 * @param       {string} eventName      EasyRTC event name.
 * @param       {...*} eventParam       The event parameters
 */
pub.events.emitDefault = function(){
    if (!pub.events.defaultListeners[arguments['0']]) {
        console.error("Error emitting listener. No default for event '" + arguments['0'] + "' exists.");
        return;
    }
    pub.events.defaultListeners[Array.prototype.shift.call(arguments)].apply(this, arguments);
};


/**
 * Resets the listener for a given event to the default listener. Removes other listeners.
 *
 * @param       {string} eventName      EasyRTC event name.
 */
pub.events.setDefaultListener = function(eventName) {
    if (!_.isFunction(pub.events.defaultListeners[eventName])) {
        console.error("Error setting default listener. No default for event '" + eventName + "' exists.");
    }
    pub.events._eventListener.removeAllListeners(eventName);
    pub.events._eventListener.on(eventName, pub.events.defaultListeners[eventName]);
};


/**
 * Resets the listener for all EasyRTC events to the default listeners. Removes all other listeners.
 */
pub.events.setDefaultListeners = function() {
    pub.events._eventListener.removeAllListeners();
    for (var currentEventName in pub.events.defaultListeners) {
        if (_.isFunction(pub.events.defaultListeners[currentEventName])) {
            pub.events._eventListener.on(currentEventName, pub.events.defaultListeners[currentEventName]);
        } else {
            throw new pub.util.ServerError("Error setting default listener. No default for event '" + currentEventName + "' exists.");
        }
    }
};


/**
 * Map of EasyRTC event listener names to their default functions. This map can be used to run a default function manually.
 */
pub.events.defaultListeners = {
    "authenticate":             eventListener.onAuthenticate,
    "authenticated":            eventListener.onAuthenticated,
    "connection" :              eventListener.onConnection,
    "disconnect":               eventListener.onDisconnect,
    "getIceConfig":             eventListener.onGetIceConfig,
    "roomCreate":               eventListener.onRoomCreate,
    "roomJoin":                 eventListener.onRoomJoin,
    "roomLeave":                eventListener.onRoomLeave,
    "log":                      eventListener.onLog,
    "shutdown":                 eventListener.onShutdown,
    "startup":                  eventListener.onStartup,

    "easyrtcAuth":              eventListener.onEasyrtcAuth,
    "easyrtcCmd":               eventListener.onEasyrtcCmd,
    "easyrtcMsg":               eventListener.onEasyrtcMsg,

    "emitEasyrtcCmd":           eventListener.onEmitEasyrtcCmd,
    "emitEasyrtcMsg":           eventListener.onEmitEasyrtcMsg,
    "emitError":                eventListener.onEmitError,

    "emitReturnAck":            eventListener.onEmitReturnAck,
    "emitReturnError":          eventListener.onEmitReturnError,
    "emitReturnToken":          eventListener.onEmitReturnToken,

    "msgTypeGetIceConfig":      eventListener.onMsgTypeGetIceConfig,
    "msgTypeGetRoomList":       eventListener.onMsgTypeGetRoomList,
    "msgTypeRoomJoin":          eventListener.onMsgTypeRoomJoin,
    "msgTypeRoomLeave":         eventListener.onMsgTypeRoomLeave,
    "msgTypeSetPresence":       eventListener.onMsgTypeSetPresence,
    "msgTypeSetRoomApiField":   eventListener.onMsgTypeSetRoomApiField
};


/**
 * Sets listener for a given EasyRTC event. Only one listener is allowed per event. Any other listeners for an event are removed before adding the new one. See the events documentation for expected listener parameters.
 *
 * @param       {string} eventName      Listener name.
 * @param       {function} listener     Function to be called when listener is fired
 */
pub.events.on = function(eventName, listener) {
    if (eventName && _.isFunction(listener)) {
        pub.events._eventListener.removeAllListeners(eventName);
        pub.events._eventListener.on(eventName, listener);
    }
    else {
        pub.util.logError("Unable to add listener to event '" + eventName + "'");
    }
};


/**
 * Removes all listeners for an event. If there is a default EasyRTC listener, it will be added. If eventName is `null`, all events will be removed than the defaults will be restored.
 *
 * @param       {?string} eventName     Listener name. If `null`, then all events will be removed.
 */
pub.events.removeAllListeners = function(eventName) {
    if (eventName) {
        pub.events.setDefaultListener(eventName);
    } else {
        pub.events.setDefaultListeners();
    }
};


/**
 * General utility functions are grouped in this util object.  For convenience, this class has also been attached to the application, connection, session, and room classes.
 * @class
 */
pub.util = {};


/**
 * Performs a deep copy of an object, returning the duplicate.
 * Do not use on objects with circular references.
 *
 * @function
 * @param       {Object} input          Input variable (or object) to be copied.
 * @returns     {Object}                New copy of variable.
 */
pub.util.deepCopy             = g.deepCopy;


/**
 * An empty dummy function, which is designed to be used as a default callback in functions when none has been provided.
 *
 * @param       {Object} input          Input variable (or object) to be copied.
 */
pub.util.nextToNowhere  =   function(err) {};

/**
 * Determines if an Error object is an instance of ApplicationError, ConnectionError, or ServerError. If it is, it will return true.
 *
 * @function
 * @param       {*|Error}               Will accept any value, but will only return true for appropriate error objects.
 * @return      {Boolean}
 */
pub.util.isError              = eu.isError;


/**
 * Determines if an Error object is an instance of ApplicationWarning, ConnectionWarning, or ServerWarning. If it is, it will return true.
 *
 * @function
 * @param       {*|Error}               Will accept any value, but will only return true for appropriate error objects.
 * @return      {Boolean}
 */
pub.util.isWarning            = eu.isWarning;


/**
 * Custom Error Object for EasyRTC Application Errors.
 *
 * @function
 * @param       {string} msg            Text message describing the error.
 */
pub.util.ApplicationError     = eu.ApplicationError;


/**
 * Custom Error Object for EasyRTC Application Warnings.
 *
 * @function
 * @param       {string} msg            Text message describing the error.
 */
pub.util.ApplicationWarning   = eu.ApplicationWarning;


/**
 * Custom Error Object for EasyRTC C Errors.
 *
 * @function
 * @param       {string} msg            Text message describing the error.
 */
pub.util.ConnectionError      = eu.ConnectionError;

/**
 * Custom Error Object for EasyRTC Connection Warnings.
 *
 * @function
 * @param       {string} msg            Text message describing the error.
 */
pub.util.ConnectionWarning    = eu.ConnectionWarning;


/**
 * Custom Error Object for EasyRTC Server Errors.
 *
 * @function
 * @param       {string} msg            Text message describing the error.
 */
pub.util.ServerError          = eu.ServerError;


/**
 * Custom Error Object for EasyRTC Server Warnings.
 *
 * @function
 * @param       {string} msg            Text message describing the error.
 */
pub.util.ServerWarning        = eu.ServerWarning;


/**
 * Returns an EasyRTC message error object for a specific error code. This is meant to be emited or returned to a websocket client.
 *
 * @param       {String} errorCode      EasyRTC error code associated with an error.
 * @return      {Object}                EasyRTC message error object for the specific error code.
 */
pub.util.getErrorMsg = function(errorCode) {
    var msg = {
        msgType: "error",
        serverTime: Date.now(),
        msgData: {
            errorCode: errorCode,
            errorText: pub.util.getErrorText(errorCode)
        }
    };

    if (!msg.msgData.errorText) {
        msg.msgData.errorText="Error occurred with error code: " + errorCode;
        pub.util.logWarning("Emitted unknown error with error code [" + errorCode + "]");
    }

    return msg;
};


/**
 * Returns human readable text for a given error code. If an unknown error code is provided, a null value will be returned.
 *
 * @param       {String} errorCode      EasyRTC error code associated with an error.
 * @return      {string}                Human readable error string
 */
pub.util.getErrorText = function(errorCode) {
    switch (errorCode) {
        case "BANNED_IP_ADDR":              return "Client IP address is banned. Socket will be disconnected."; break;
        case "LOGIN_APP_AUTH_FAIL":         return "Authentication for application failed. Socket will be disconnected."; break;
        case "LOGIN_BAD_APP_NAME":          return "Provided application name is improper. Socket will be disconnected."; break;
        case "LOGIN_BAD_AUTH":              return "Authentication for application failed. Socket will be disconnected."; break;
        case "LOGIN_BAD_ROOM":              return "Requested room is invalid or does not exist. Socket will be disconnected."; break;
        case "LOGIN_BAD_STRUCTURE":         return "Authentication for application failed. The provided structure is improper. Socket will be disconnected."; break;
        case "LOGIN_BAD_USER_CFG":          return "Provided configuration options improper or invalid. Socket will be disconnected."; break;
        case "LOGIN_GEN_FAIL":              return "Authentication failed. Socket will be disconnected."; break;
        case "LOGIN_NO_SOCKETS":            return "No sockets available for account. Socket will be disconnected."; break;
        case "LOGIN_TIMEOUT":               return "Login has timed out. Socket will be disconnected."; break;
        case "MSG_REJECT_BAD_DATA":         return "Message rejected. The provided msgData is improper."; break;
        case "MSG_REJECT_BAD_ROOM":         return "Message rejected. Requested room is invalid or does not exist."; break;
        case "MSG_REJECT_BAD_FIELD":        return "Message rejected. Problem with field structure or name."; break;
        case "MSG_REJECT_BAD_SIZE":         return "Message rejected. Packet size is too large."; break;
        case "MSG_REJECT_BAD_STRUCTURE":    return "Message rejected. The provided structure is improper."; break;
        case "MSG_REJECT_BAD_TYPE":         return "Message rejected. The provided msgType is unsupported."; break;
        case "MSG_REJECT_GEN_FAIL":         return "Message rejected. General failure occured."; break;
        case "MSG_REJECT_NO_AUTH":          return "Message rejected. Not logged in or client not authorized."; break;
        case "MSG_REJECT_NO_ROOM_LIST":     return "Message rejected. Room list unavailable."; break;
        case "MSG_REJECT_PRESENCE":         return "Message rejected. Presence could could not be set."; break;
        case "MSG_REJECT_TARGET_EASYRTCID": return "Message rejected. Target easyrtcid is invalid, not using same application, or no longer online."; break;
        case "MSG_REJECT_TARGET_GROUP":     return "Message rejected. Target group is invalid or not defined."; break;
        case "MSG_REJECT_TARGET_ROOM":      return "Message rejected. Target room is invalid or not created."; break;
        case "SERVER_SHUTDOWN":             return "Server is being shutdown. Socket will be disconnected."; break;
        default:
            pub.util.logWarning("Unknown message errorCode requested [" + errorCode + "]");
            return null;
    }
};


/**
 * General logging function which emits a log event so long as the log level has a severity equal or greater than e.option.logLevel
 *
 * @param       {string} level          Log severity level. Can be ("debug"|"info"|"warning"|"error")
 * @param       {string} logText        Text for log.
 * @param       {?*} [logFields]        Simple JSON object which contains extra fields to be logged.
 */
pub.util.log = function(level, logText, logFields) {
    switch(e.option.logLevel) {
        case "error":
        if (level !="error") {break;}

        case "warning":
        if (level =="info" ) {break;}

        case "info":
        if (level =="debug") {break;}

        case "debug":
        pub.events.emit("log", level, logText, logFields);
    }
};


/**
 * Convenience function for logging "debug" level items.
 *
 * @param       {string} logText        Text for log.
 * @param       {?*} [logFields]        Simple JSON object which contains extra fields to be logged.
 */
pub.util.logDebug = function(logText, logFields) {
    pub.util.log("debug", logText, logFields);
};


/**
 * Convenience function for logging "info" level items.
 *
 * @param       {string} logText        Text for log.
 * @param       {?*} [logFields]        Simple JSON object which contains extra fields to be logged.
 */
pub.util.logInfo = function(logText, logFields) {
    pub.util.log("info", logText, logFields);
};


/**
 * Convenience function for logging "warning" level items.
 *
 * @param       {string} logText        Text for log.
 * @param       {?*} [logFields]        Simple JSON object which contains extra fields to be logged.
 */
pub.util.logWarning = function(logText, logFields) {
    pub.util.log("warning", logText, logFields);
};


/**
 * Convenience function for logging "error" level items.
 *
 * @param       {string} logText        Text for log.
 * @param       {?*} [logFields]        Simple JSON object which contains extra fields to be logged.
 */
pub.util.logError = function(logText, logFields) {
    pub.util.log("error", logText, logFields);
};


/**
 *  Checks with EasyRTC site for latest version. Writes to the log if a version can be found. If connection cannot be established than no error will be shown.
 */
pub.util.updateCheck = function() {
    var easyrtcVersion = pub.getVersion();

    require("http").get("http://easyrtc.com/version/?app=easyrtc&ver=" + easyrtcVersion + "&platform=" + process.platform + "&nodever=" + process.version, function(res) {
        if (res.statusCode == 200)
            res.on('data', function(latestVersion) {
                latestVersion = (latestVersion+"").replace(/[^0-9a-z.]/g,"");
                if (latestVersion != easyrtcVersion) {
                    var l = latestVersion.replace(/[^0-9.]/g, "").split(".", 3);
                    l[0] = parseInt(l[0]);
                    l[1] = parseInt(l[1]);
                    l[2] = parseInt(l[2]);
                    var v = easyrtcVersion.replace(/[^0-9.]/g, "").split(".", 3);
                    v[0] = parseInt(v[0]);
                    v[1] = parseInt(v[1]);
                    v[2] = parseInt(v[2]);
                    if (v[0] < l[0] || (v[0] == l[0] && v[1] < l[1]) || (v[0] == l[0] && v[1] == l[1] && v[2] < l[2]))
                        pub.util.logWarning("Update Check: New version of easyRTC is available (" + latestVersion + "). Visit http://easyrtc.com/");
                    else if (v[0] == l[0] && v[1] == l[1] && v[2] == l[2] && easyrtcVersion.replace(/[^a-z]/gi, "") != "")
                        pub.util.logWarning("Update Check: New non-beta version of easyRTC is available (" + latestVersion + "). Visit http://easyrtc.com/");
                }
            });
    }).on('error', function(e){});
};


/**
 * Checks an incoming EasyRTC message to determine if it is syntactically valid.
 *
 * @param       {string} type           The Socket.IO message type. Expected values are (easyrtcAuth|easyrtcCmd|easyrtcMsg)
 * @param       {Object} msg            Message object which contains the full message from a client; this can include the standard msgType and msgData fields.
 * @param       {?Object} appObj        EasyRTC application object. Contains methods used for identifying and managing an application.
 * @param       {function(Error, {boolean}, {string})} callback Callback with error, a boolean of whether message if valid, and a string indicating the error code if the message is invalid.
 */
pub.util.isValidIncomingMessage = function(type, msg, appObj, callback) {
    // A generic getOption variable which points to the getOption function at either the top or application level
    var getOption = (_.isObject(appObj) ? appObj.getOption : pub.getOption);

    // All messages follow the basic structure
    if (!_.isString(type)) {
        callback(null, false, "MSG_REJECT_BAD_TYPE");
        return;
    }
    if (!_.isObject(msg)) {
        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
        return;
    }
    if (!_.isString(msg.msgType)) {
        callback(null, false, "MSG_REJECT_BAD_TYPE");
        return;
    }

    switch (type) {
        case "easyrtcAuth":
            if (msg.msgType != "authenticate") {
                callback(null, false, "MSG_REJECT_BAD_TYPE");
                return;
            }
            if (!_.isObject(msg.msgData)) {
                callback(null, false, "MSG_REJECT_BAD_DATA");
                return;
            }

            // msgData.apiVersion (required)
            if (msg.msgData.apiVersion === undefined || !_.isString(msg.msgData.apiVersion) ||  !getOption("apiVersionRegExp").test(msg.msgData.apiVersion)) {
                callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                return;
            }

            // msgData.appName
            if (msg.msgData.applicationName !== undefined && (!_.isString(msg.msgData.applicationName) ||  !getOption("appNameRegExp").test(msg.msgData.applicationName))) {
                callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                return;
            }

            // msgData.easyrtcsid
            if (msg.msgData.easyrtcsid !== undefined && (!_.isString(msg.msgData.easyrtcsid) ||  !getOption("easyrtcsidRegExp").test(msg.msgData.easyrtcsid))) {
                callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                return;
            }

            var isCallbackRun = false;
            async.waterfall([
                function(asyncCallback){
                    if (!appObj){
                        pub.app((msg.msgData.applicationName !== undefined?msg.msgData.applicationName:getOption("appDefaultName")), function(err, newAppObj){
                            if (!err) {
                                appObj = newAppObj;
                                getOption = appObj.getOption;
                            }
                            asyncCallback(null);
                        });
                    }
                    else {
                        asyncCallback(null);
                    }
                },
                function(asyncCallback){
                    // msgData.username
                    if (msg.msgData.username !== undefined && (!_.isString(msg.msgData.username) ||  !getOption("usernameRegExp").test(msg.msgData.username))) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        isCallbackRun = true;
                        return;
                    }
    
                    // msgData.credential
                    if (msg.msgData.credential !== undefined && (!_.isObject(msg.msgData.credential) ||  _.isEmpty(msg.msgData.credential))) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        isCallbackRun = true;
                        return;
                    }
    
                    // msgData.roomJoin
                    if (msg.msgData.roomJoin !== undefined) {
                        if (!_.isObject(msg.msgData.roomJoin)){
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            isCallbackRun = true;
                            return;
                        }

                        for (var currentRoomName in msg.msgData.roomJoin) {
                            if (!getOption("roomNameRegExp").test(currentRoomName) || !_.isObject(msg.msgData.roomJoin[currentRoomName]) || !_.isString(msg.msgData.roomJoin[currentRoomName].roomName) || currentRoomName != msg.msgData.roomJoin[currentRoomName].roomName) {
                                callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                                isCallbackRun = true;
                                return;
                            }
                        };
                    }

                    // msgData.setPresence
                    if (msg.msgData.setPresence !== undefined) {
                        if (!_.isObject(msg.msgData.setPresence) || _.isEmpty(msg.msgData.setPresence)) {
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            isCallbackRun = true;
                            return;
                        }
                        if (msg.msgData.setPresence.show !== undefined && (!_.isString(msg.msgData.setPresence.show) || !getOption("presenceShowRegExp").test(msg.msgData.setPresence.show))) {
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            isCallbackRun = true;
                            return;
                        }
                        if (msg.msgData.setPresence.status !== undefined && (!_.isString(msg.msgData.setPresence.status) || !getOption("presenceStatusRegExp").test(msg.msgData.setPresence.status))) {
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            isCallbackRun = true;
                            return;
                        }
                    }

                    // TODO: setUserCfg
                    if (msg.msgData.setUserCfg !== undefined) {
                    }
                    asyncCallback(null);

                }
            ],
                function(err){
                    if (err) {
                        if (!isCallbackRun) {
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            isCallbackRun = true;
                            return;
                        }
                    }
                    else {
                        // Incoming message syntactically valid
                        callback(null, true, null);
                        return;
                    }
                }
            );

            return;
            break;

        case "easyrtcCmd":
            switch (msg.msgType) {
                case "candidate" :
                case "offer" :
                case "answer" :
                    // candidate, offer, and answer each require a non-empty msgData object and a proper targetEasyrtcid
                    if (!_.isObject(msg.msgData) || _.isEmpty(msg.msgData)) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isString(msg.targetEasyrtcid) || !getOption("easyrtcidRegExp").test(msg.targetEasyrtcid)) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    break;
                case "reject" :
                case "hangup" :
                    // reject, and hangup each require a targetEasyrtcid but no msgData
                    if (msg.msgData !== undefined) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isString(msg.targetEasyrtcid) || !getOption("easyrtcidRegExp").test(msg.targetEasyrtcid)) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    break;

                case "getIceConfig" :
                    if (msg.msgData !== undefined && !_.isEmpty(msg.msgData )) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    break;

                case "getRoomList" :
                    if (msg.msgData !== undefined) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    break;

                case "roomJoin" :
                    if (!_.isObject(msg.msgData)) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isObject(msg.msgData.roomJoin)){
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }

                    for (var currentRoomName in msg.msgData.roomJoin) {
                        if (!getOption("roomNameRegExp").test(currentRoomName) || !_.isObject(msg.msgData.roomJoin[currentRoomName]) || !_.isString(msg.msgData.roomJoin[currentRoomName].roomName) || currentRoomName != msg.msgData.roomJoin[currentRoomName].roomName) {
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            return;
                        }
                    };
                    break;

                case "roomLeave" :
                    if (!_.isObject(msg.msgData)) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isObject(msg.msgData.roomLeave)){
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }

                    for (var currentRoomName in msg.msgData.roomLeave) {
                        if (!getOption("roomNameRegExp").test(currentRoomName) || !_.isObject(msg.msgData.roomLeave[currentRoomName]) || !_.isString(msg.msgData.roomLeave[currentRoomName].roomName) || currentRoomName != msg.msgData.roomLeave[currentRoomName].roomName) {
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            return;
                        }
                    };
                    break;

                case "setPresence" :
                    if (!_.isObject(msg.msgData)) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isObject(msg.msgData.setPresence) || _.isEmpty(msg.msgData.setPresence)) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    if (msg.msgData.setPresence.show !== undefined && (!_.isString(msg.msgData.setPresence.show) || !getOption("presenceShowRegExp").test(msg.msgData.setPresence.show))) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    if (msg.msgData.setPresence.status !== undefined && (!_.isString(msg.msgData.setPresence.status) || !getOption("presenceStatusRegExp").test(msg.msgData.setPresence.status))) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    break;

                case "setRoomApiField" :
                    if (!_.isObject(msg.msgData)) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isObject(msg.msgData.setRoomApiField) || _.isEmpty(msg.msgData.setRoomApiField)) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    if (!_.isString(msg.msgData.setRoomApiField.roomName) || !getOption("roomNameRegExp").test(msg.msgData.setRoomApiField.roomName)) {
                        callback(null, false, "MSG_REJECT_BAD_ROOM");
                        return;
                    }
                    if (msg.msgData.setRoomApiField.field !== undefined){
                        if (!_.isObject(msg.msgData.setRoomApiField.field)){
                            callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                            return;
                        }
                        try {
                            if (JSON.stringify(msg.msgData.setRoomApiField.field).length >= 4096) {
                                callback(null, false, "MSG_REJECT_BAD_SIZE");
                                return;
                            }
                        } catch (e){
                            if (!_.isObject(msg.msgData.setRoomApiField.field)){
                                callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                                return;
                            }
                        }
                    }
                    break;

                case "setUserCfg" :
                    if (!_.isObject(msg.msgData)) {
                        callback(null, false, "MSG_REJECT_BAD_DATA");
                        return;
                    }
                    if (!_.isObject(msg.msgData.setUserCfg) || _.isEmpty(msg.msgData.setUserCfg)) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }

                    // setUserCfg.p2pList
                    if (msg.msgData.setUserCfg.p2pList !== undefined && (!_.isObject(msg.msgData.setUserCfg.p2pList) || _.isEmpty(msg.msgData.setUserCfg.p2pList))) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }
                    // TODO: Go through p2pList to confirm key's are easyrtcid's

                    // setUserCfg.userSettings
                    if (msg.msgData.setUserCfg.userSettings !== undefined && (!_.isObject(msg.msgData.setUserCfg.userSettings) || _.isEmpty(msg.msgData.setUserCfg.userSettings))) {
                        callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
                        return;
                    }

                    break;

                default:
                    // Reject all unknown msgType's
                    callback(null, false, "MSG_REJECT_BAD_TYPE");
                    return;
            }

            break;

        case "easyrtcMsg":
            // targetEasyrtcid
            if (msg.targetEasyrtcid !== undefined && (!_.isString(msg.targetEasyrtcid) || !getOption("easyrtcidRegExp").test(msg.targetEasyrtcid))) {
                callback(null, false, "MSG_REJECT_TARGET_EASYRTCID");
                return;
            }
            // targetGroup
            if (msg.targetGroup !== undefined && (!_.isString(msg.targetGroup) || !getOption("groupNameRegExp").test(msg.targetGroup))) {
                callback(null, false, "MSG_REJECT_TARGET_GROUP");
                return;
            }
            // targetRoom
            if (msg.targetRoom !== undefined && (!_.isString(msg.targetRoom) || !getOption("roomNameRegExp").test(msg.targetRoom))) {
                callback(null, false, "MSG_REJECT_TARGET_ROOM");
                return;
            }
            break;

        default:
            callback(null, false, "MSG_REJECT_BAD_TYPE");
            return;
    }

    // Incoming message syntactically valid
    callback(null, true, null);
};


/**
 * Will attempt to deliver an EasyRTC sessionid via a cookie. Requires that session management be enabled from within Express. 
 *
 * @param       {Object} req            Http request object
 * @param       {Object} res            Http result object
 */
pub.util.sendSessionCookie = function(req, res) {
    // If sessions or session cookies are disabled, return without an error.
    if (!pub.getOption("sessionEnable") || !pub.getOption("sessionCookieEnable")) {
        return;
    }
    if (req.sessionID && (!req.cookies || !req.cookies["easyrtcsid"] || req.cookies["easyrtcsid"] != req.sessionID)) {
        try{
            pub.util.logDebug("Sending easyrtcsid cookie ["+ req.sessionID +"] to [" + req.ip + "] for request [" + req.url + "]");
            res.cookie("easyrtcsid", req.sessionID, { maxAge: 2592000000, httpOnly: false});
        }catch(e){
            pub.util.logWarning("Problem setting easyrtcsid cookie ["+ req.sessionID +"] to [" + req.ip + "] for request [" + req.url + "]");
        }
    }
};


/**
 * Determine if a given application name has been defined.
 *
 * @param       {string} appName        Application name which uniquely identifies it on the server.
 * @param       {function(Error, {boolean})} callback Callback with error and boolean of whether application is defined.
 */
pub.isApp = function (appName, callback) {
    callback(null, (e.app[appName]?true:false));
};


/**
 * Creates a new EasyRTC application with default values. If a callback is provided, it will receive the new application object.
 *
 * The callback may receive an Error object if unsuccessful. Depending on the severity, known errors have an "instanceof" ApplicationWarning or ApplicationError.
 *
 * @param       {string} appName        Application name which uniquely identifies it on the server.
 * @param       {?object} options       Options object with options to apply to the application. May be null.
 * @param       {appCallback} [callback] Callback with error and application object
 */
pub.createApp = function(appName, options, callback) {
    if (!_.isFunction(callback)) {
        callback = function(err, appObj) {};
    }
    if (!appName || !pub.getOption("appNameRegExp").test(appName)) {
        pub.util.logWarning("Can not create application with improper name: '" + appName +"'");
        callback(new pub.util.ApplicationWarning("Can not create application with improper name: '" + appName +"'"));
        return;
    }
    if (e.app[appName]) {
        pub.util.logWarning("Can not create application which already exists: '" + appName +"'");
        callback(new pub.util.ApplicationWarning("Can not create application which already exists: '" + appName +"'"));
        return;
    }
    if (!_.isObject(options)){
        options = {};
    }

    pub.util.logDebug("Creating application: '" + appName +"'");

    e.app[appName] = {
        appName:    appName,
        connection: {},
        field:      {},
        group:      {},
        option:     {},
        room:       {},
        session:    {}
    };

    // Get the new app object
    pub.app(appName, function(err, appObj) {
        if (err){
            callback(err);
            return;
        }

        // Set all options in options object. If any fail, an error will be sent to the callback.
        async.each(Object.keys(options), function(currentOptionName, asyncCallback){
            appObj.setOption(currentOptionName, options[currentOptionName]);
            asyncCallback(null);
        },
        function(err) {
            if(err) {
                callback(new pub.util.AppicationError("Could not set options when creating application: '" + appName +"'", err));
                return;
            }
            // Set default application fields
            var appDefaultFieldObj = appObj.getOption("appDefaultFieldObj");
            if(_.isObject(appDefaultFieldObj)) {
                for (var currentFieldName in appDefaultFieldObj) {
                    appObj.setField(
                        currentFieldName,
                        appDefaultFieldObj[currentFieldName].fieldValue,
                        appDefaultFieldObj[currentFieldName].fieldOption,
                        null
                    );
                };
            }

            if (appObj.getOption("roomDefaultEnable")) {
                // Create default room
                appObj.createRoom( appObj.getOption("roomDefaultName"),
                    null,
                    function(err, roomObj) {
                        if (err){
                            callback(err);
                            return;
                        }
                        // Return app object to callback
                        callback(null, appObj);
                    }
                );
            }
            else {
                // Return app object to callback
                callback(null, appObj);
            }
        });
    });
};


/**
 * Contains the methods for interfacing with an EasyRTC application.
 *
 * The callback will receive an application object upon successful retrieval of application.
 *
 * The callback may receive an Error object if unsuccessful. Depending on the severity, known errors have an "instanceof" ApplicationWarning or ApplicationError.
 *
 * The function does return an application object which is useful for chaining, however the callback approach is safer and provides additional information in the event of an error.
 *
 * @param       {?string} appName        Application name which uniquely identifies it on the server. Uses default application if null.
 * @param       {appCallback} [callback] Callback with error and application object
 */
pub.app = function(appName, callback) {

    /**
     * The primary method for interfacing with an EasyRTC application.
     *
     * @class       appObj
     * @memberof    pub
     */
    var appObj = {};
    if (!appName) {
        appName = pub.getOption("appDefaultName");
    }
    if (!_.isFunction(callback)) {
        callback = function(err, appObj) {};
    }
    if (!e.app[appName]) {
        pub.util.logWarning("Attempt to request non-existent application name: '" + appName +"'");
        callback(new pub.util.ApplicationWarning("Attempt to request non-existent application name: '" + appName +"'"));
        return;
    }


    /**
     * Expose all event functions
     * 
     * @memberof    pub.appObj
     */
    appObj.events = pub.events;


    /**
     * Expose all utility functions
     * 
     * @memberof    pub.appObj
     */
    appObj.util = pub.util;


    /**
     * Returns the application name for the application. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
     *
     * @memberof    pub.appObj
     * @return      {string}    The application name.
     */
    appObj.getAppName = function() {
        return appName;
    };


    /**
     * Returns an array of all easyrtcid's connected to the application
     *
     * @memberof    pub.appObj
     * @param       {function(Error, Array.<string>)} callback Callback with error and array of easyrtcid's.
     */
    appObj.getConnectionEasyrtcids = function(callback) {
        var easyrtcids = new Array();
        for (var key in e.app[appName].connection) {
            easyrtcids.push(key);
        };
        callback(null, easyrtcids);
    };


    /**
     * Returns application level field object for a given field name.
     *
     * @memberof    pub.appObj
     * @param       {string}    Field name
     * @param       {function(Error, Object)} callback Callback with error and field value (any type)
     */
    appObj.getField = function(fieldName, callback) {
        if (!e.app[appName].field[fieldName]) {
            pub.util.logDebug("Can not find app field: '" + fieldName +"'");
            callback(new pub.util.ApplicationWarning("Can not find app field: '" + fieldName +"'"));
            return;
        }
        callback(null, pub.util.deepCopy(e.app[appName].field[fieldName]));
    };


    /**
     * Returns an object containing all field names and values within the application. Can be limited to fields with isShared option set to true.
     *
     * @memberof    pub.appObj
     * @param       {boolean}   limitToIsShared Limits returned fields to those which have the isShared option set to true.
     * @param       {function(Error, object)} callback Callback with error and object containing field names and values.
     */
    appObj.getFields = function(limitToIsShared, callback) {
        var fieldObj = {};
        for (var fieldName in e.app[appName].field) {
            if (!limitToIsShared || e.app[appName].field[fieldName].fieldOption.isShared) {
                fieldObj[fieldName] = {
                    fieldName:fieldName,
                    fieldValue: pub.util.deepCopy(e.app[appName].field[fieldName].fieldValue)
                };
            }
        };
        callback(null, fieldObj);
    };


    /**
     * Returns an array of all group names within the application
     *
     * @memberof    pub.appObj
     * @param       {function(Error, Array.<string>)} callback Callback with error and array of group names.
     */
    appObj.getGroupNames = function(callback) {
        var groupNames = new Array();
        for (var key in e.app[appName].group) {
            groupNames.push(key);
        };
        callback(null, groupNames);
    };


    /**
     * Gets individual option value. Will first check if option is defined for the application, else it will revert to the global level option.
     *
     * @memberof    pub.appObj
     * @param       {String}    option      Option name
     * @return      {*}                     Option value (can be any JSONable type)
     */
    appObj.getOption = function(optionName) {
        return ((e.app[appName].option[optionName] === undefined) ? pub.getOption(optionName) : (e.app[appName].option[optionName]));
    };


    /**
     * Returns an array of all room names within the application.
     *
     * @memberof    pub.appObj
     * @param       {function(Error, Array.<string>)} callback Callback with error and array of room names.
     */
    appObj.getRoomNames = function(callback) {
        var roomNames = new Array();
        for (var key in e.app[appName].room) {
            roomNames.push(key);
        };
        callback(null, roomNames);
    };


    /**
     * Returns an array of all easyrtcsid's within the application
     *
     * @memberof    pub.appObj
     * @param       {function(Error, Array.<string>)} callback Callback with error and array containing easyrtcsid's.
     */
    appObj.getEasyrtcsids = function(callback) {
        var easyrtcsids = new Array();
        for (var key in e.app[appName].session) {
            easyrtcsids.push(key);
        };
        callback(null, easyrtcsids);
    };

    /**
     * Returns an array of all easyrtcsid's within the application. Old SessionKey name kept for transition purposes. Use getEasyrtcsid();
     *
     * @memberof    pub.appObj
     * @ignore
     */
    appObj.getSessionKeys = appObj.getEasyrtcsids;


    /**
     * Gets connection status for a connection. It is possible for a connection to be considered connected without being authenticated.
     *
     * @memberof    pub.appObj
     * @param       {string}    easyrtcid   EasyRTC unique identifier for a socket connection.
     * @param       {function(Error, Boolean)} callback Callback with error and a boolean indicating if easyrtcid is connected.
     */
    appObj.isConnected = function(easyrtcid, callback) {
        if(e.app[appName] && e.app[appName].connection && e.app[appName].connection[easyrtcid]){
            callback(null, true);
        } else {
            callback(null, false);
        }
    };


    /**
     * Sets individual option. Set value to NULL to delete the option (thus reverting to global option).
     *
     * @memberof    pub.appObj
     * @param       {String}    optionName  Option name
     * @param       {?*}        optionValue Option value
     * @return      {Boolean}               true on success, false on failure
     */
    appObj.setOption = function(optionName, optionValue) {
        // Can only set options which currently exist
        if (typeof e.option[optionName] == "undefined") {
            pub.util.logError("Error setting option. Unrecognised option name '" + optionName + "'.");
            return false;
         }

        // If value is null, delete option from application (reverts to global option)
        if (optionValue == null) {
            if (!(e.app[appName].option[optionName] === 'undefined')) {
                delete e.app[appName].option[optionName];
            }
        } else {
            // Set the option value to be a full deep copy, thus preserving private nature of the private EasyRTC object.
            e.app[appName].option[optionName] = pub.util.deepCopy(optionValue);
        }
        return true;
    };


    /**
     * Sets application field value for a given field name.
     *
     * @memberof    pub.appObj
     * @param       {string}    fieldName       Must be formatted according to "fieldNameRegExp" option.
     * @param       {Object}    fieldValue
     * @param       {?Object}   fieldOption     Field options (such as isShared which defaults to false)
     * @param       {nextCallback} [next]       A success callback of form next(err).
     */
    appObj.setField = function(fieldName, fieldValue, fieldOption, next) {
        pub.util.logDebug("["+appName+"] Setting field [" + fieldName + "]", fieldValue);
        if (!_.isFunction(next)) {
            next = pub.util.nextToNowhere;
        }

        if (!pub.getOption("fieldNameRegExp").test(fieldName)) {
            pub.util.logWarning("Can not create application field with improper name: '" + fieldName +"'");
            next(new pub.util.ApplicationWarning("Can not create application field with improper name: '" + fieldName +"'"));
            return;
        }
        e.app[appName].field[fieldName] = {
            fieldName:fieldName,
            fieldValue:fieldValue,
            fieldOption:{isShared: ((_.isObject(fieldOption) && fieldOption.isShared)?true:false)}
        };

        next(null);
    };


    /**
     * Gets connection object for a given connection key. Returns null if connection not found.
     * The returned connection object includes functions for managing connection fields.
     *
     * @memberof    pub.appObj
     * @param       {string}    easyrtcid   EasyRTC unique identifier for a socket connection.
     * @param       {connectionCallback} callback Callback with error and object containing EasyRTC connection object.
     */
    appObj.connection = function(easyrtcid, callback) {
        if (!e.app[appName].connection[easyrtcid]) {
            pub.util.logWarning("Attempt to request non-existent connection key: '" + easyrtcid +"'");
            callback(new pub.util.ConnectionWarning("Attempt to request non-existent connection key: '" + easyrtcid +"'"));
            return;
        }
        if (!pub.socketServer || !pub.socketServer.sockets.sockets[easyrtcid] || pub.socketServer.sockets.sockets[easyrtcid].disconnected) {
            pub.util.logWarning("Attempt to request non-existent socket: '" + easyrtcid +"'");
            callback(new pub.util.ConnectionWarning("Attempt to request non-existent socket: '" + easyrtcid +"'"));
            return;
        }
        if (pub.socketServer.sockets.sockets[easyrtcid].disconnected) {
            pub.util.logWarning("Attempt to request disconnected socket: '" + easyrtcid +"'");
            callback(new pub.util.ConnectionWarning("Attempt to request disconnected socket: '" + easyrtcid +"'"));
            return;
        }


        /**
         * @class       connectionObj
         * @memberof    pub.appObj
         */
        var connectionObj = {};

        // House the local session object
        var _sessionObj;

        /**
         * Expose all event functions
         * 
         * @memberof    pub.appObj.connectionObj
         */
        connectionObj.events = pub.events;


        /**
         * Expose all utility functions
         * 
         * @memberof    pub.appObj.connectionObj
         */
        connectionObj.util = pub.util;


        /*
         * Reference to connection's socket.io object.
         *
         * @memberof    pub.appObj.connectionObj
         */
        connectionObj.socket = pub.socketServer.sockets.sockets[easyrtcid];

        /**
         * Returns the application object to which the connection belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.connectionObj
         * @return      {Object}    The application object
         */
        connectionObj.getApp = function() {
            return appObj;
        };


        /**
         * Returns the application name for the application to which the connection belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.connectionObj
         * @return      {string}    The application name
         */
        connectionObj.getAppName = function() {
            return appName;
        };


        /**
         * Returns the session object to which the connection belongs (if one exists). Returns a null if connection is not attached to a session (such as when sessions are disabled). Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.connectionObj
         * @return      {Object}    The session object. May be null if connection has not been joined to a session.
         */
        connectionObj.getSession = function() {
            return _sessionObj;
        };

        /**
         * Returns the easyrtcid for the connection.  Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.connectionObj
         * @return      {string}    Returns the connection's easyrtcid, which is the EasyRTC unique identifier for a socket connection.
         */
        connectionObj.getEasyrtcid = function() {
            return easyrtcid;
        };


        /**
         * Returns connection level field object for a given field name.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {string}    Field name
         * @param       {function(Error, Object)} callback Callback with error and field value (any type)
         */
        connectionObj.getField = function(fieldName, callback) {
            if (!e.app[appName].connection[easyrtcid].field[fieldName]) {
                pub.util.logDebug("Can not find connection field: '" + fieldName +"'");
                callback(new pub.util.ApplicationWarning("Can not find connection field: '" + fieldName +"'"));
                return;
            }
            callback(null, pub.util.deepCopy(e.app[appName].connection[easyrtcid].field[fieldName]));
        };


        /**
         * Returns an object containing all field names and values within the connection. Can be limited to fields with isShared option set to true.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {boolean}   limitToIsShared Limits returned fields to those which have the isShared option set to true.
         * @param       {function(Error, object)} callback Callback with error and object containing field names and values.
         */
        connectionObj.getFields = function(limitToIsShared, callback) {
            var fieldObj = {};
            for (var fieldName in e.app[appName].connection[easyrtcid].field) {
                if (!limitToIsShared || e.app[appName].connection[easyrtcid].field[fieldName].fieldOption.isShared) {
                    fieldObj[fieldName] = {
                        fieldName:fieldName,
                        fieldValue: pub.util.deepCopy(e.app[appName].connection[easyrtcid].field[fieldName].fieldValue)
                    };
                }
            };
            callback(null, fieldObj);
        };


        /**
         * Returns an array of all room names which connection has entered.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {function(Error, Array.<string>)} callback Callback with error and array of room names.
         */
        connectionObj.getRoomNames = function(callback) {
            var roomNames = new Array();
            for (var key in e.app[appName].connection[easyrtcid].room) {
                roomNames.push(key);
            };
            callback(null, roomNames);
        };


        /**
         * TO BE REMOVED - Use getSession() instead.
         * Returns the session object which the connection belongs to. Will return null if connection is not in a session (such as if session handling is disabled).
         * 
         * @ignore
         * @memberof    pub.appObj.connectionObj
         * @param       {function(Error, ?Object)} callback Callback with error and Session object
         */
        connectionObj.getSessionObj = function(callback) {
            if (e.app[appName].connection[easyrtcid] && e.app[appName].connection[easyrtcid].toSession && e.app[appName].connection[easyrtcid].toSession.easyrtcsid){
                appObj.session(e.app[appName].connection[easyrtcid].toSession.easyrtcsid, callback);
            }
            else {
                callback(null,null);
            }
        };


        /**
         * Sets connection authentication status for the connection.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {Boolean}   isAuthenticated True/false as to if the connection should be considered authenticated.
         * @param       {nextCallback} next         A success callback of form next(err).
         */
        connectionObj.joinSession = function(easyrtcsid, next) {
            if (!e.app[appName].session[easyrtcsid]) {
                next(new pub.util.ConnectionWarning("["+appName+"]["+easyrtcid+"] Session [" + easyrtcsid + "] does not exist. Could not join session"));
                return;
            }

            appObj.session(easyrtcsid, function(err, sessionObj){
                if (err) {
                    next(err);
                    return;
                }

                e.app[appName].connection[easyrtcid].toSession = e.app[appName].session[easyrtcsid];

                // Set local session object
                _sessionObj = sessionObj;

                next(null);
            });
        };

        /**
         * Sets connection authentication status for the connection.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {Boolean}   isAuthenticated True/false as to if the connection should be considered authenticated.
         * @param       {nextCallback} next         A success callback of form next(err).
         */
        connectionObj.setAuthenticated = function(isAuthenticated, next) {
            if (isAuthenticated == true) {
                e.app[appName].connection[easyrtcid].isAuthenticated = true;
            } else {
                e.app[appName].connection[easyrtcid].isAuthenticated = false;
            }
            next(null);
        };


        /**
         * Sets the credential for the connection.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {?*}        credential      Credential for the connection. Can be any JSONable object.
         * @param       {nextCallback} next         A success callback of form next(err).
         */
        connectionObj.setCredential = function(credential, next) {
            e.app[appName].connection[easyrtcid].credential = credential;
            next(null);
        };


        /**
         * Sets connection field value for a given field name.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {string}    fieldName       Must be formatted according to "fieldNameRegExp" option.
         * @param       {Object}    fieldValue
         * @param       {?Object}   fieldOption     Field options (such as isShared which defaults to false)
         * @param       {nextCallback} [next]       A success callback of form next(err). Possible err will be instanceof (ApplicationWarning).
         */
        connectionObj.setField = function(fieldName, fieldValue, fieldOption, next) {
            pub.util.logDebug("["+appName+"]["+easyrtcid+"] - Setting field [" + fieldName + "]", fieldValue);
            if (!_.isFunction(next)) {
                next = pub.util.nextToNowhere;
            }

            if (!pub.getOption("fieldNameRegExp").test(fieldName)) {
                pub.util.logWarning("Can not create connection field with improper name: '" + fieldName +"'");
                next(new pub.util.ApplicationWarning("Can not create connection field with improper name: '" + fieldName +"'"));
                return;
            }

            e.app[appName].connection[easyrtcid].field[fieldName] = {
                fieldName:fieldName,
                fieldValue:fieldValue,
                fieldOption:{isShared: ((_.isObject(fieldOption) && fieldOption.isShared)?true:false)}
            };

            next(null);
        };


        /**
         * Sets the presence object for the connection.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {Object}    presenceObj     A presence object.
         * @param       {nextCallback} next         A success callback of form next(err).
         */
        connectionObj.setPresence = function(presenceObj, next) {
            if (presenceObj.show !== undefined) {
                e.app[appName].connection[easyrtcid].presence.show = presenceObj.show;
            }
            if (presenceObj.status !== undefined) {
                e.app[appName].connection[easyrtcid].presence.status = presenceObj.status;
            }
            if (presenceObj.type !== undefined) {
                e.app[appName].connection[easyrtcid].presence.type = presenceObj.type;
            }
            next(null);
        };


        /**
         * Sets the username string for the connection.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {?string}   username        Username to assign to the connection.
         * @param       {nextCallback} next         A success callback of form next(err).
         */
        connectionObj.setUsername = function(username, next) {
            e.app[appName].connection[easyrtcid].username = username;
            next(null);
        };


        /**
         * Emits the roomData message with a clientListDelta for the current connection to other connections in rooms this connection is in.
         * Note: To send listDetas for individual rooms, use connectionRoomObj.emitRoomDataDelta
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {Boolean}   isLeavingAllRooms   Indicator if connection is leaving all rooms. Meant to be used upon disconnection / logoff.
         * @param       {Object}    callback        Callback of form (err, roomDataObj) which will contain the roomDataObj including all updated rooms of the connection and is designed to be returnable to the connection.
         */
        connectionObj.emitRoomDataDelta = function(isLeavingAllRooms, callback) {
            pub.util.logDebug("["+appName+"]["+easyrtcid+"] Running func 'connectionObj.emitRoomDataDelta'");
            if (!_.isFunction(callback)) {
                callback = function(err, roomDataObj) {};
            }


            var fullRoomDataDelta = {};

            var otherClients = {};


            // Generate a complete roomDelta for the current client
            connectionObj.generateRoomDataDelta(isLeavingAllRooms, function(err, newFullRoomDataDelta){
                fullRoomDataDelta = newFullRoomDataDelta;

                // Running callback right away so client doesn't have to wait to continue
                callback(null, fullRoomDataDelta);

                // Populate otherClients object with other clients who share room(s)
                for (var currentRoomName in fullRoomDataDelta) {
                    for (var currentEasyrtcid in e.app[appName].room[currentRoomName].clientList) {
                        if (otherClients[currentEasyrtcid] === undefined) {
                            otherClients[currentEasyrtcid] = {};
                        }
                        otherClients[currentEasyrtcid][currentRoomName] = true;
                    }
                }

                // Emit custom roomData object to each client who shares a room with the current client
                for (var currentEasyrtcid in otherClients) {
                    var msg = {
                        "msgData":{
                            "roomData":{}
                        }
                    };

                    for (var currentRoomName in otherClients[currentEasyrtcid]) {
                        if (fullRoomDataDelta[currentRoomName]) {
                            msg.msgData.roomData[currentRoomName] = fullRoomDataDelta[currentRoomName];
                        }
                    }

                    connectionObj.getApp().connection(currentEasyrtcid, function(err, emitToConnectionObj){
                        if (!err && currentEasyrtcid != easyrtcid && emitToConnectionObj) {
                            pub.events.emit("emitEasyrtcCmd", emitToConnectionObj, "roomData", msg, null, function(){});
                        }
                    });
                }
            });
        };


        /**
         * Generates a full room clientList object for the given connection
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {?string}   [roomStatus="join"] Room status which allow for values of "join"|"update"|"leave".
         * @param       {?Object}   roomMap     Map of rooms to generate connection clientList for. If null, then all rooms will be used.
         * @param       {Object}    callback    Callback which includes a formed roomData object .
         */
        connectionObj.generateRoomClientList = function(roomStatus, roomMap, callback) {
            if (!_.isString(roomStatus)) {
                roomStatus = "join";
            }

            if (!_.isObject(roomMap)) {
                roomMap = e.app[appName].connection[easyrtcid].room;
            }

            var roomData = {};

            for (var currentRoomName in e.app[appName].connection[easyrtcid].room) {
                // If room is not in the provided roomMap, then skip it.
                if (!roomMap[currentRoomName]) {
                    continue;
                }

                var connectionRoom = e.app[appName].connection[easyrtcid].room[currentRoomName];
                roomData[currentRoomName] = {
                    "roomName":         currentRoomName,
                    "roomStatus":       roomStatus,
                    "clientList":       {}
                };

                // Empty current clientList
                connectionRoom.clientList = {};

                // Fill connection clientList, and roomData clientList for current room
                for (var currentEasyrtcid in connectionRoom.toRoom.clientList) {

                    var currentToConnection = connectionRoom.toRoom.clientList[currentEasyrtcid].toConnection;

                    connectionRoom.clientList[currentEasyrtcid] = {
                        "toConnection": currentToConnection
                    };

                    roomData[currentRoomName].clientList[currentEasyrtcid] = {
                        "easyrtcid":    currentEasyrtcid,
                        "roomJoinTime": currentToConnection.room[currentRoomName].enteredOn,
                        "presence":     currentToConnection.presence
                    };

                    if (currentToConnection.room[currentRoomName] && (!_.isEmpty(currentToConnection.room[currentRoomName].apiField))){
                        roomData[currentRoomName].clientList[currentEasyrtcid].apiField= currentToConnection.room[currentRoomName].apiField;
                    }

                    if(currentToConnection.username) {
                        roomData[currentRoomName].clientList[currentEasyrtcid].username= currentToConnection.username;
                    }
                }

                // Include room fields (with isShared set to true)
                for (var fieldName in connectionRoom.toRoom.field) {
                    if (_.isObject(connectionRoom.toRoom.field[fieldName].fieldOption) && connectionRoom.toRoom.field[fieldName].fieldOption.isShared) {
                        if (!_.isObject(roomData[currentRoomName].field)) {
                            roomData[currentRoomName].field = {};
                        }
                        roomData[currentRoomName].field[fieldName] = {
                            "fieldName":  fieldName,
                            "fieldValue": pub.util.deepCopy(connectionRoom.toRoom.field[fieldName].fieldValue)
                        };
                    }
                };

                // Updating timestamp of when clientList was retrieved. Useful for sending delta's later on.
                connectionRoom.gotListOn = Date.now();
            };
            callback(null, roomData);
        };


        /**
         * Generates a delta roomData object for the current user including all rooms the user is in. The result can be selectively parsed to deliver delta roomData objects to other clients.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {Boolean}   isLeavingRoom   Indicates if connection is in the process of leaving the room.
         * @param       {Object}    callback        Callback of form (err, roomDataDelta).
         */
        connectionObj.generateRoomDataDelta = function(isLeavingRoom, callback) {
            pub.util.logDebug("["+appName+"]["+easyrtcid+"] Running func 'connectionObj.generateRoomDataDelta'");

            var roomDataDelta = {};

            // set the roomData's clientListDelta for each room the client is in
            for (var currentRoomName in e.app[appName].connection[easyrtcid].room) {
                roomDataDelta[currentRoomName] = {
                    "roomName":         currentRoomName,
                    "roomStatus":       "update",
                    "clientListDelta":  {}
                };

                if (isLeavingRoom) {
                    roomDataDelta[currentRoomName].clientListDelta.removeClient = {};
                    roomDataDelta[currentRoomName].clientListDelta.removeClient[easyrtcid] = {"easyrtcid":easyrtcid};
                } else {
                    roomDataDelta[currentRoomName].clientListDelta.updateClient = {};
                    roomDataDelta[currentRoomName].clientListDelta.updateClient[easyrtcid] = {
                        "easyrtcid":    easyrtcid,
                        "roomJoinTime": e.app[appName].connection[easyrtcid].room[currentRoomName].enteredOn,
                        "presence":     e.app[appName].connection[easyrtcid].presence
                    };

                    if(!_.isEmpty(e.app[appName].connection[easyrtcid].apiField)) {
                       roomDataDelta[currentRoomName].clientListDelta.updateClient[easyrtcid].apiField= e.app[appName].connection[easyrtcid].apiField;
                    }
                    if(e.app[appName].connection[easyrtcid].username) {
                        roomDataDelta[currentRoomName].clientListDelta.updateClient[easyrtcid].username= e.app[appName].connection[easyrtcid].username;
                    }
                }
            }

            callback(null, roomDataDelta);
        };


        /**
         * Generates the roomList message object
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {function(Error, Object)} callback Callback with error and roomList object.
         */
        connectionObj.generateRoomList = function(callback) {
            pub.util.logDebug("["+appName+"]["+easyrtcid+"] Running func 'connectionObj.generateRoomList'");
            var roomList = {};

            for (var currentRoomName in e.app[appName].room) {
                roomList[currentRoomName] = {
                    "roomName":currentRoomName,
                    "numberClients": _.size(e.app[appName].room[currentRoomName].clientList)
                };
            };
            callback(null, roomList);
        };


        /**
         * Gets connection authentication status for the connection. It is possible for a connection to become disconnected and keep the authenticated flag. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.connectionObj
         * @returns     Boolean     Authentication status
         */
        connectionObj.isAuthenticated = function(callback) {
            if (e.app[appName].connection[easyrtcid] && e.app[appName].connection[easyrtcid].isAuthenticated == true) {
                return true;
            } else {
                return false;
            }
        };


        /**
         * Gets connection status for the connection. It is possible for a connection to be considered connected without being authenticated. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.connectionObj
         * @returns     Boolean     Connection status
         */
        connectionObj.isConnected = function(callback) {
            if (connectionObj.socket && connectionObj.socket.socket){
                return connectionObj.socket.socket.connected;
            }
            else {
                return false;
            }
        };


        /**
         * Returns a boolean to the callback indicating if connection is in a given group. NOT YET IMPLEMENTED
         * @ignore
         * @memberof    pub.appObj.connectionObj
         * @param       {string}    groupName Group name to check.
         * @param       {function(Error, Boolean)} callback Callback with error and a boolean indicating if connection is in a room..
         */
        connectionObj.isInGroup = function(groupName, callback) {
            if (_.isString(groupName) && e.app[appName].connection[easyrtcid].group[groupName] !== undefined) {
                callback(null, true);
            }
            else {
                callback(null, false);
            }
        };


        /**
         * Returns a boolean to the callback indicating if connection is in a given room
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {string} roomName       Room name which uniquely identifies a room within an EasyRTC application.
         * @param       {function(Error, Boolean)} callback Callback with error and a boolean indicating if connection is in a room..
         */
        connectionObj.isInRoom = function(roomName, callback) {
            if (_.isString(roomName) && e.app[appName].connection[easyrtcid].room[roomName] !== undefined) {
                callback(null, true);
            }
            else {
                callback(null, false);
            }
        };


        /**
         * Joins an existing room, returning a room object. Returns null if room can not be joined.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {string} roomName       Room name which uniquely identifies a room within an EasyRTC application.
         * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC connection room object (same as calling room(roomName))
         */
        connectionObj.joinRoom = function(roomName, callback) {
            if (!roomName || !appObj.getOption("roomNameRegExp").test(roomName)) {
                pub.util.logWarning("["+appName+"]["+easyrtcid+"] Can not enter room with improper name: '" + roomName +"'");
                callback(new pub.util.ConnectionWarning("Can not enter room with improper name: '" + roomName +"'"));
                return;
            }

            // Check if client already in room
            if (e.app[appName].connection[easyrtcid].room[roomName]) {
                connectionObj.room(roomName, callback);
                return;
            }


            // Local private function to create the default connection-room object in the private variable
            var createConnectionRoom = function(roomName, appRoomObj, callback) {
                // Join room. Creates a default connection room object
                e.app[appName].connection[easyrtcid].room[roomName] = {
                    apiField:           {},
                    enteredOn:          Date.now(),
                    gotListOn:          Date.now(),
                    clientList:         {},
                    userfields:         {},
                    toRoom:             e.app[appName].room[roomName]
                };

                // Add easyrtcid to room clientList
                e.app[appName].room[roomName].clientList[easyrtcid] = {
                    enteredOn:      Date.now(),
                    modifiedOn:     Date.now(),
                    toConnection:   e.app[appName].connection[easyrtcid]
                };

                // Returns connection room object to callback.
                connectionObj.room(roomName, callback);
            };

            // Check if room doesn't exist
            if (!e.app[appName].room[roomName]) {
                if (appObj.getOption("roomAutoCreateEnable")) {
                        appObj.createRoom(roomName, null, function(err, roomObj){
                            if (err) {
                                callback(err);
                                return;
                            }
                            createConnectionRoom(roomName, roomObj, callback);
                        });
                } else {
                    pub.util.logWarning("["+appName+"]["+easyrtcid+"] Can not enter room which doesn't exist: '" + roomName +"'");
                    callback(new pub.util.ConnectionWarning("Can not enter room which doesn't exist: '" + roomName +"'"));
                    return;
                }
            }

            appObj.room(roomName, function(err, appRoomObj){
                if(err) {
                    callback(err);
                    return;
                }
                createConnectionRoom(roomName, appRoomObj, callback);
            });

        };


        /**
         * Gets room object for a given room name. Returns null if room not found.
         * The returned room object includes functions for managing room fields.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {string} roomName       Room name which uniquely identifies a room within an EasyRTC application.
         * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC connection room object.
         */
        connectionObj.room = function(roomName, callback) {
            if (_.isUndefined(e.app[appName].connection[easyrtcid].room[roomName])) {
                pub.util.logWarning("Attempt to request non-existent room name: '" + roomName +"'");
                callback(new pub.util.ConnectionWarning("Attempt to request non-existent room name: '" + roomName +"'"));
                return;
            }

            /**
             * This is a gateway object connecting connections to the rooms they are in.
             *
             * @class       connectionRoomObj
             * @memberof    pub.appObj.connectionObj
             */
            var connectionRoomObj = {};

            // House the local room object
            var _roomObj;


            /**
             * Expose all event functions
             * 
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             */
            connectionRoomObj.events = pub.events;


            /**
             * Expose all utility functions
             * 
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             */
            connectionRoomObj.util = pub.util;


            /**
             * Returns the application object to which the connection belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @return      {Object}    The application object
             */
            connectionRoomObj.getApp = function() {
                return appObj;
            };


            /**
             * Returns the application name for the application to which the connection belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @return      {string}    The application name
             */
            connectionRoomObj.getAppName = function() {
                return appName;
            };


            /**
             * Returns the connection object to which the connection belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @return      {Object}    The application object
             */
            connectionRoomObj.getConnection = function() {
                return connectionObj;
            };


            /**
             * Returns the room object to which the connection belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @return      {Object}    The room object
             */
            connectionRoomObj.getRoom = function() {
                return _roomObj;
            };


            /**
             * Leaves the current room. Any room variables will be lost.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @param       {nextCallback} [next]   A success callback of form next(err).
             */
            connectionRoomObj.leaveRoom = function(next) {
                if (!_.isFunction(next)) {
                    next = pub.util.nextToNowhere;
                }

                e.app[appName].room[roomName].modifiedOn = Date.now();
                delete e.app[appName].room[roomName].clientList[easyrtcid];
                delete e.app[appName].connection[easyrtcid].room[roomName];

                connectionRoomObj.emitRoomDataDelta(true, function(err, roomDataObj){
                    next(err);
                });
            };


            /**
             * Emits the roomData message with a clientListDelta for the current connection to other connections in the same room.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @param       {boolean}   isLeavingRoom   Is connection leaving the room?
             */
            connectionRoomObj.emitRoomDataDelta = function(isLeavingRoom, callback) {
                pub.util.logDebug("["+appName+"]["+easyrtcid+"] Room [" + roomName + "] Running func 'connectionRoomObj.emitRoomDataDelta'");
                if (!_.isFunction(callback)) {
                    callback = function(err, roomDataObj) {};
                }

                connectionRoomObj.generateRoomDataDelta(isLeavingRoom, function(err, roomDataDelta) {
                    if (err) {
                        callback(err);
                        return;
                    }

                    var msg = {"msgData":{"roomData": {}}};
                    msg.msgData.roomData[roomName] = roomDataDelta;

                    for (var currentEasyrtcid in e.app[appName].room[roomName].clientList) {
                        connectionObj.getApp().connection(currentEasyrtcid, function(err, emitToConnectionObj){
                            if (!err && currentEasyrtcid != easyrtcid && emitToConnectionObj) {
                                pub.events.emit("emitEasyrtcCmd", emitToConnectionObj, "roomData", msg, null, function(){});
                            }
                        });
                    }
                    callback(null, roomDataDelta);
                });
            };


            /**
             * Generated the roomData[room] message with a clientListDelta for the current connection to other connections in the same room.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @param       {boolean}   isLeavingRoom   Is connection leaving the room?
             */
            connectionRoomObj.generateRoomDataDelta = function(isLeavingRoom, callback) {
                pub.util.logDebug("["+appName+"]["+easyrtcid+"] Room [" + roomName + "] Running func 'connectionRoomObj.generateRoomDataDelta'");
                if (!_.isFunction(callback)) {
                    next = pub.util.nextToNowhere;
                }
                var roomDataDelta = {"roomName":roomName, "roomStatus":"update", "clientListDelta":{}};

                if (isLeavingRoom) {
                    roomDataDelta.clientListDelta.removeClient = {};
                    roomDataDelta.clientListDelta.removeClient[easyrtcid] = {"easyrtcid":easyrtcid};
                } else {
                    var connectionRoom = e.app[appName].connection[easyrtcid].room[roomName];
                    roomDataDelta.clientListDelta.updateClient = {};
                    roomDataDelta.clientListDelta.updateClient[easyrtcid] = {
                        "easyrtcid":      easyrtcid,
                        "roomJoinTime":   e.app[appName].connection[easyrtcid].room[roomName].enteredOn,
                        "presence":       e.app[appName].connection[easyrtcid].presence
                    };

                    if(!_.isEmpty(e.app[appName].connection[easyrtcid].room[roomName].apiField)) {
                        roomDataDelta.clientListDelta.updateClient[easyrtcid].apiField= e.app[appName].connection[easyrtcid].room[roomName].apiField;
                    }
                    if(e.app[appName].connection[easyrtcid].username) {
                        roomDataDelta.clientListDelta.updateClient[easyrtcid].username= e.app[appName].connection[easyrtcid].username;
                    }
                }

                callback(null, roomDataDelta);
            };

            /**
             * Sets the API field for the current connection in a room.
             *
             * @memberof    pub.appObj.connectionObj.connectionRoomObj
             * @param       {boolean}   isLeavingRoom   Is connection leaving the room?
             */
            connectionRoomObj.setApiField = function(apiFieldObj, next){
                if (!_.isFunction(next)) {
                    next = pub.util.nextToNowhere;
                }

                e.app[appName].connection[easyrtcid].room[roomName].apiField = pub.util.deepCopy(apiFieldObj);
                next(null);
            };

            // Set the roomObj before returning the connectioRoomObj
            appObj.room(roomName,
                function(err, roomObj){
                    _roomObj = roomObj;
                    callback(null, connectionRoomObj);
                }
            );
        };


        /**
         * Removes a connection object. Does not (currently) remove connection from rooms or groups.
         *
         * @memberof    pub.appObj.connectionObj
         * @param       {nextCallback} next         A success callback of form next(err).
         */
        connectionObj.removeConnection = function(next) {
            if(e.app[appName] && _.isObject(e.app[appName].connection) && e.app[appName].connection[easyrtcid]){
                e.app[appName].connection[easyrtcid].isAuthenticated = false;
                delete e.app[appName].connection[easyrtcid];
            }
            next(null);
        };


        // Before returning connectionObj, join the connection to a session (if available).
        if (e.app[appName].connection[easyrtcid].toSession) {
            appObj.session(e.app[appName].connection[easyrtcid].toSession.easyrtcsid, function(err, sessionObj){
                if (err) {
                    callback(err);
                    return;
                }
                _sessionObj = sessionObj;
                callback(null, connectionObj);
            });
        } else {
            callback(null, connectionObj);
        }
    };


    /**
     * Creates a new connection with a provided connection key
     *
     * @memberof    pub.appObj
     * @param       {string}    easyrtcid   EasyRTC unique identifier for a socket connection.
     * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC connection object (same as calling connection(easyrtcid))
     */
    appObj.createConnection = function(easyrtcid, callback) {
        if (!easyrtcid || !appObj.getOption("easyrtcidRegExp").test(easyrtcid)) {
            pub.util.logWarning("Can not create connection with improper name: '" + easyrtcid +"'");
            callback(new pub.util.ConnectionWarning("Can not create connection with improper name: '" + easyrtcid +"'"));
            return;
        }

        if (e.app[appName].connection[easyrtcid]) {
            pub.util.logWarning("Can not create connection which already exists: '" + easyrtcid +"'");
            callback(new pub.util.ConnectionWarning("Can not create connection which already exists: '" + easyrtcid +"'"));
            return;
        }

        // Set the connection structure with some default values
        e.app[appName].connection[easyrtcid] = {
            easyrtcid:      easyrtcid,
            connectOn:      Date.now(),
            isAuthenticated:false,
            userName:       null,
            credential:     null,
            field:          {},
            group:          {},
            presence: {
                show:       "chat",
                status:     null
            },
            room:           {},
            toApp: e.app[appName]
        };

        // Initialize a new connection object
        appObj.connection(easyrtcid, function(err, connectionObj) {
            if(err) {
                callback(err);
                return;
            }

            // Set default connection fields
            var connectionDefaultFieldObj = appObj.getOption("connectionDefaultFieldObj");
            if(_.isObject(connectionDefaultFieldObj)) {
                for (var currentFieldName in connectionDefaultFieldObj) {
                    connectionObj.setField(
                        currentFieldName,
                        connectionDefaultFieldObj[currentFieldName].fieldValue,
                        connectionDefaultFieldObj[currentFieldName].fieldOption,
                        null
                    );
                };
            }

            callback(null, connectionObj);
        });
    };


    /**
     * Creates a new room, sending the resulting room object to a provided callback.
     *
     * @memberof    pub.appObj
     * @param       {string} roomName       Room name which uniquely identifies a room within an EasyRTC application.
     * @param       {?object}   options     Options object with options to apply to the room. May be null.
     * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC room object (same as calling appObj.room(roomName))
     */
    appObj.createRoom = function(roomName, options, callback) {
        if (!roomName || !appObj.getOption("roomNameRegExp").test(roomName)) {
            pub.util.logWarning("Can not create room with improper name: '" + roomName +"'");
            callback(new pub.util.ApplicationWarning("Can not create room with improper name: '" + roomName +"'"));
            return;
        }
        if (e.app[appName].room[roomName]) {
            pub.util.logWarning("Can not create room which already exists: '" + roomName +"'");
            callback(new pub.util.ApplicationWarning("Can not create room which already exists: '" + roomName +"'"));
            return;
        }
        if (!_.isObject(options)){
            options = {};
        }
        pub.util.logDebug("Creating room: '" + roomName +"' with options:", options);

        e.app[appName].room[roomName] = {
            roomName:           roomName,
            clientList:         {},
            field:              {},
            option:             {},
            modifiedOn:         Date.now()
        };

        // Initialize a new room object
        appObj.room(roomName, function(err, roomObj) {
            if (err){
                callback(err);
                return;
            }

            // Set all options in options object. If any fail, an error will be sent to the callback.
            async.each(Object.keys(options), function(currentOptionName, asyncCallback){
                roomObj.setOption(currentOptionName, options[currentOptionName]);
                asyncCallback(null);
            },
            function(err) {
                if(err) {
                    callback(new pub.util.AppicationError("Could not set options when creating room: '" + roomName +"'", err));
                    return;
                }

                // Set default room fields
                var roomDefaultFieldObj = roomObj.getOption("roomDefaultFieldObj");

                if(_.isObject(roomDefaultFieldObj)) {
                    for (var currentFieldName in roomDefaultFieldObj) {
                        roomObj.setField(
                            currentFieldName,
                            roomDefaultFieldObj[currentFieldName].fieldValue,
                            roomDefaultFieldObj[currentFieldName].fieldOption,
                            null
                        );
                    };
                }

                // Return room object to callback
                callback(null, roomObj);
            });
        });
    };


    /**
     * Creates a new session with a provided easyrtcsid
     *
     * @memberof    pub.appObj
     * @param       {string}    easyrtcsid  EasyRTC Session Identifier. Must be formatted according to "easyrtcsidRegExp" option.
     * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC session object (same as calling session(easyrtcsid))
     */
    appObj.createSession = function(easyrtcsid, callback) {
        pub.util.logDebug("["+appObj.getAppName()+"] Creating session ["+easyrtcsid+"]");

        if (!easyrtcsid || !appObj.getOption("easyrtcsidRegExp").test(easyrtcsid)) {
            pub.util.logWarning("Can not create session with improper name ["+easyrtcsid+"]");
            callback(new pub.util.ConnectionWarning("Can not create session with improper name ["+easyrtcsid+"]"));
            return;
        }

        if (e.app[appName].session[easyrtcsid]) {
            pub.util.logWarning("Can not create session which already exists ["+easyrtcsid+"]");
            callback(new pub.util.ConnectionWarning("Can not create session which already exists ["+easyrtcsid+"]"));
            return;
        }

        // Set the session structure with some default values
        e.app[appName].session[easyrtcsid] = {
            "easyrtcsid": easyrtcsid,
            "startOn"   : Date.now(),
            "field"     : {}
        };

        appObj.session(easyrtcsid, callback);
    };


    /**
     * Checks if a provided room is defined. The callback returns a boolean if room is defined
     *
     * @memberof    pub.appObj
     * @param       {string} roomName       Room name which uniquely identifies a room within an EasyRTC application.
     * @param       {function(Error, {boolean})} callback Callback with error and boolean of whether room is defined.
     */
    appObj.isRoom = function (roomName, callback) {
        callback(null, (e.app[appName].room[roomName]?true:false));
    };


    /**
     * Checks if a provided session is defined. The callback returns a boolean if session is defined
     *
     * @memberof    pub.appObj
     * @param       {function(Error, {boolean})} callback Callback with error and boolean of whether session is defined.
     */
    appObj.isSession = function (easyrtcsid, callback) {
        callback(null, (e.app[appName].session[easyrtcsid]?true:false));
    };


    /**
     * NOT YET IMPLEMENTED - Gets group object for a given group name. Returns null if group not found.
     * The returned group object includes functions for managing group fields.
     *
     * @memberof    pub.appObj
     * @param       {string}    Group name
     * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC group object.
     */
    appObj.group = function(groupName, callback) {
        if (!e.app[appName].group[groupName]) {
            pub.util.logWarning("Attempt to request non-existent group name: '" + groupName +"'");
            callback(new pub.util.ApplicationWarning("Attempt to request non-existent group name: '" + groupName +"'"));
            return;
        }

        var groupObj = {};


        /**
         * Expose all event functions
         */
        groupObj.events = pub.events;


        /**
         * Expose all utility functions
         */
        groupObj.util = pub.util;


        /**
         * NOT YET IMPLEMENTED - Returns an array of all connected clients within the room.
         *
         * @ignore
         * @param {function(Error, Array.<string>)} callback Callback with error and array containing all easyrtcid's.
         */
        groupObj.getConnections = function(callback) {
            var connectedEasyrtcidArray = new Array();
            for (var key in e.app[appName].group[groupName].clientList) {
                connectedEasyrtcidArray.push(key);
            };
            callback(null, connectedEasyrtcidArray);
        };

        callback(null, groupObj);
    };


    /**
     * Gets room object for a given room name. Returns null if room not found.
     * The returned room object includes functions for managing room fields.
     *
     * @memberof    pub.appObj
     * @param       {string} roomName       Room name which uniquely identifies a room within an EasyRTC application.
     * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC room object.
     */
    appObj.room = function(roomName, callback) {
        if (!e.app[appName].room[roomName]) {
            pub.util.logWarning("Attempt to request non-existent room name: '" + roomName +"'");
            callback(new pub.util.ApplicationWarning("Attempt to request non-existent room name: '" + roomName +"'"));
            return;
        }

        /**
         * EasyRTC Room Object. Contains methods for handling a specific room including determining which connections have joined.
         *
         * @class       roomObj
         * @memberof    pub.appObj
         */
        var roomObj = {};


        /**
         * Expose all event functions
         *
         * @memberof    pub.appObj.roomObj
         */
        roomObj.events = pub.events;


        /**
         * Expose all utility functions
         *
         * @memberof    pub.appObj.roomObj
         */
        roomObj.util = pub.util;


        /**
         * Returns the application object to which the room belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.roomObj
         * @return      {Object}    The application object
         */
        roomObj.getApp = function() {
            return appObj;
        };


        /**
         * Returns the application name for the application to which the room belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.roomObj
         * @return      {string}    The application object
         */
        roomObj.getAppName = function() {
            return appName;
        };


        /**
         * INCOMPLETE: Emits a roomData message containing fields to all connections in the current room. This is meant to be called after a room field has been set or updated. 
         * @ignore 
         */
        roomObj.emitRoomDataFieldUpdate = function(skipEasyrtcid, next){
            roomObj.getFields(true, function(err, fieldObj){
                if (err) {
                    next(err);
                    return;
                }

                var outgoingMsg = {"msgData":{"roomData": {}}};
                outgoingMsg.msgData.roomData[roomName] = {
                    "roomName":     roomName,
                    "roomStatus":   "update"
                };
                outgoingMsg.msgData.roomData[roomName].field = fieldObj;

                async.each(
                    Object.keys(e.app[appName].room[roomName].clientList),
                    function(currentEasyrtcid, asyncCallback) {

                        // Skip a given easyrtcid?
                        if(skipEasyrtcid && (skipEasyrtcid == currentEasyrtcid)){
                            asyncCallback(null);
                            return;
                        }

                        // Retrieve a connection object, then send the roomData message.
                        appObj.connection(currentEasyrtcid, function(err, targetConnectionObj){
                            pub.events.emit("emitEasyrtcCmd", targetConnectionObj, "roomData", outgoingMsg, function(msg){}, function(err){
                                // Ignore errors if unable to send to a socket. 
                                asyncCallback(null);
                            });
                        });
                    },
                    function(err) {
                        next(null);
                    }
                );
            });
        };


        /**
         * Returns room level field object for a given field name.
         *
         * @memberof    pub.appObj.roomObj
         * @param       {string}    Field name
         * @param       {function(Error, Object)} callback Callback with error and field value (any type)
         */
        roomObj.getField = function(fieldName, callback) {
            if (!e.app[appName].room[roomName].field[fieldName]) {
                pub.util.logDebug("Can not find room field: '" + fieldName +"'");
                callback(new pub.util.ApplicationWarning("Can not find room field: '" + fieldName +"'"));
                return;
            }
            callback(null, pub.util.deepCopy(e.app[appName].room[roomName].field[fieldName]));
        };


        /**
         * Returns an object containing all field names and values within the room. Can be limited to fields with isShared option set to true.
         *
         * @memberof    pub.appObj.roomObj
         * @param       {boolean}   limitToIsShared Limits returned fields to those which have the isShared option set to true.
         * @param       {function(Error, object)} callback Callback with error and object containing field names and values.
         */
        roomObj.getFields = function(limitToIsShared, callback) {
            var fieldObj = {};
            for (var fieldName in e.app[appName].room[roomName].field) {
                if (!limitToIsShared || e.app[appName].room[roomName].field[fieldName].fieldOption.isShared) {
                    fieldObj[fieldName] = {
                        fieldName:fieldName,
                        fieldValue: pub.util.deepCopy(e.app[appName].room[roomName].field[fieldName].fieldValue)
                    };
                }
            };
            callback(null, fieldObj);
        };


        /**
         * Gets individual option value. Will first check if option is defined for the room, else it will revert to the application level option (which will in turn fall back to the global level).
         *
         * @memberof    pub.appObj.roomObj
         * @param       {String}    option      Option name
         * @return      {*}         Option value (can be any type)
         */
        roomObj.getOption = function(optionName) {
            return ((e.app[appName].room[roomName].option[optionName] === undefined) ? appObj.getOption(optionName) : (e.app[appName].room[roomName].option[optionName]));
        };


        /**
         * Sets individual option which applies only to this room. Set value to NULL to delete the option (thus reverting to global option)
         *
         * @memberof    pub.appObj.roomObj
         * @param       {Object}    option      Option name
         * @param       {Object}    value       Option value
         * @return      {Boolean}               true on success, false on failure
         */
         roomObj.setOption = function(optionName, optionValue) {
            // Can only set options which currently exist
            if (typeof e.option[optionName] == "undefined") {
                pub.util.logError("Error setting option. Unrecognised option name '" + optionName + "'.");
                return false;
             }

            // If value is null, delete option from application (reverts to global option)
            if (optionValue == null) {
                if (!(e.app[appName].option[optionName] === undefined)) {
                    delete e.app[appName].room[roomName].option[optionName];
                }
            } else {
                // Set the option value to be a full deep copy, thus preserving private nature of the private EasyRTC object.
                e.app[appName].room[roomName].option[optionName] = pub.util.deepCopy(optionValue);
            }
            return true;
        };


        /**
         * Sets application field value for a given field name. Returns false if field could not be set.
         *
         * @memberof    pub.appObj.roomObj
         * @param       {string}    easyrtcid   EasyRTC unique identifier for a socket connection.
         * @param       {nextCallback} next     A success callback of form next(err).
         */
        roomObj.setConnection = function(easyrtcid, next) {
            e.app[appName].room[roomName].clientList[easyrtcid] = {enteredOn:Date.now()};
            next(null);
        };


        /**
         * Sets room field value for a given field name.
         *
         * @memberof    pub.appObj.roomObj
         * @param       {string}    fieldName       Must be formatted according to "fieldNameRegExp" option.
         * @param       {Object}    fieldValue
         * @param       {?Object}   fieldOption     Field options (such as isShared which defaults to false)
         * @param       {nextCallback} [next]       A success callback of form next(err). Possible err will be instanceof (ApplicationWarning).
         */
        roomObj.setField = function(fieldName, fieldValue, fieldOption, next) {
            pub.util.logDebug("["+appName+"] Room [" + roomName + "] - Setting field [" + fieldName + "]", fieldValue);
            if (!_.isFunction(next)) {
                next = pub.util.nextToNowhere;
            }

            if (!pub.getOption("fieldNameRegExp").test(fieldName)) {
                pub.util.logWarning("Can not create room field with improper name: '" + fieldName +"'");
                next(new pub.util.ApplicationWarning("Can not create room field with improper name: '" + fieldName +"'"));
                return;
            }

            e.app[appName].room[roomName].field[fieldName] = {
                fieldName:fieldName,
                fieldValue:fieldValue,
                fieldOption:{isShared: ((_.isObject(fieldOption) && fieldOption.isShared)?true:false)}
            };

            next(null);
        };


        /**
         * Returns an array containing the easyrtcid's of all connected clients within the room.
         *
         * @memberof    pub.appObj.roomObj
         * @param {function(Error, Array.<string>)} callback Callback with error and array containing all easyrtcid's.
         */
        roomObj.getConnections = function(callback) {
            var connectedEasyrtcidArray = new Array();
            for (var key in e.app[appName].room[roomName].clientList) {
                connectedEasyrtcidArray.push(key);
            };
            callback(null, connectedEasyrtcidArray);
        };


        /**
         * Returns an array containing the connectionObj's of all connected clients within the room.
         *
         * @memberof    pub.appObj.roomObj
         * @param {function(Error, Array.<Object>)} callback Callback with error and array containing connectionObj's.
         */
        roomObj.getConnectionObjects = function(callback) {
            var connectedObjArray = new Array();
            async.each(Object.keys(e.app[appName].room[roomName].clientList),
                function(currentEasyrtcid, asyncCallback) {
                    appObj.connection(currentEasyrtcid, function(err, connectionObj){
                        if (err){
                            // We will silently ignore errors
                            asyncCallback(null);
                            return;
                        }
                        // If there is no error, than push the connection object.
                        connectedObjArray.push(connectionObj);
                    });
                },
                function(err){
                    callback(null, connectedObjArray);
                }
            );
        };

        callback(null, roomObj);
    };


    /**
     * NOT YET IMPLEMENTED - Gets session object for a given easyrtcsid. Returns null if session not found.
     * The returned session object includes functions for managing session fields.
     *
     * @memberof    pub.appObj
     * @param       {string}    easyrtcsid
     * @param       {function(Error, Object)} callback Callback with error and object containing EasyRTC session object.
     */
    appObj.session = function(easyrtcsid, callback) {

        if (!e.app[appName].session[easyrtcsid]) {
            pub.util.logWarning("Attempt to request non-existent easyrtcsid: '" + easyrtcsid +"'");
            callback(new pub.util.ApplicationWarning("Attempt to request non-existent easyrtcsid: '" + easyrtcsid +"'"));
            return;
        }

        /**
         * The primary method for interfacing with an EasyRTC session.
         *
         * @class       sessionObj
         * @memberof    pub.appObj
         */
        var sessionObj = {};


        /**
         * Expose all event functions
         *
         * @memberof    pub.appObj.sessionObj
         */
        sessionObj.events = pub.events;


        /**
         * Expose all utility functions
         *
         * @memberof    pub.appObj.sessionObj
         */
        sessionObj.util = pub.util;


        /**
         * Returns the application object to which the session belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.sessionObj
         * @return      {Object}    The application object
         */
        sessionObj.getApp = function() {
            return appObj;
        };


        /**
         * Returns the application name for the application to which the session belongs. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.sessionObj
         * @return      {string}    The application name
         */
        sessionObj.getAppName = function() {
            return appName;
        };


        /**
         * Returns the easyrtcsid for the session.  Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
         *
         * @memberof    pub.appObj.sessionObj
         * @return      {string}    Returns the easyrtcsid, which is the EasyRTC unique identifier for a session.
         */
        sessionObj.getEasyrtcsid = function() {
            return easyrtcsid;
        };

        /**
         * Returns the easyrtcsid for the session. Old SessionKey name kept for transition purposes. Use getEasyrtcsid();
         * 
         * @memberof    pub.appObj.sessionObj
         * @ignore
         */
        sessionObj.getSessionKey = sessionObj.getEasyrtcsid;


        /**
         * Returns session level field object for a given field name.
         *
         * @memberof    pub.appObj.sessionObj
         * @param       {string}    Field name
         * @param       {function(Error, Object)} callback Callback with error and field value (any type)
         */
        sessionObj.getField = function(fieldName, callback) {
            if (!e.app[appName].session[easyrtcsid].field[fieldName]) {
                pub.util.logDebug("Can not find session field: '" + fieldName +"'");
                callback(new pub.util.ApplicationWarning("Can not find session field: '" + fieldName +"'"));
                return;
            }
            callback(null, pub.util.deepCopy(e.app[appName].session[easyrtcsid].field[fieldName]));
        };


        /**
         * Returns an object containing all field names and values within the session. Can be limited to fields with isShared option set to true.
         *
         * @memberof    pub.appObj.sessionObj
         * @param       {boolean}   limitToIsShared Limits returned fields to those which have the isShared option set to true.
         * @param       {function(Error, object)} callback Callback with error and object containing field names and values.
         */
        sessionObj.getFields = function(limitToIsShared, callback) {
            var fieldObj = {};
            for (var fieldName in e.app[appName].session[easyrtcsid].field) {
                if (!limitToIsShared || e.app[appName].session[easyrtcsid].field[fieldName].fieldOption.isShared) {
                    fieldObj[fieldName] = {
                        fieldName:fieldName,
                        fieldValue: pub.util.deepCopy(e.app[appName].session[easyrtcsid].field[fieldName].fieldValue)
                    };
                }
            };
            callback(null, fieldObj);
        };


        /**
         * Sets session field value for a given field name.
         *
         * @memberof    pub.appObj.sessionObj
         * @param       {string}    fieldName       Must be formatted according to "fieldNameRegExp" option.
         * @param       {Object}    fieldValue
         * @param       {?Object}   fieldOption     Field options (such as isShared which defaults to false)
         * @param       {nextCallback} [next]       A success callback of form next(err). Possible err will be instanceof (ApplicationWarning).
         */
        sessionObj.setField = function(fieldName, fieldValue, fieldOption, next) {
            pub.util.logDebug("["+appName+"] Session [" + easyrtcsid + "] - Setting field [" + fieldName + "]", fieldValue);
            if (!_.isFunction(next)) {
                next = pub.util.nextToNowhere;
            }

            if (!pub.getOption("fieldNameRegExp").test(fieldName)) {
                pub.util.logWarning("Can not create session field with improper name: '" + fieldName +"'");
                next(new pub.util.ApplicationWarning("Can not create session field with improper name: '" + fieldName +"'"));
                return;
            }

            e.app[appName].session[easyrtcsid].field[fieldName] = {
                fieldName:fieldName,
                fieldValue:fieldValue,
                fieldOption:{isShared: ((_.isObject(fieldOption) && fieldOption.isShared)?true:false)}
            };

            next(null);
        };

        callback(null, sessionObj);
    };

    callback(null, appObj);
};


// Documenting global callbacks
/**
 * The next callback is called upon completion of a method. If the `err` parameter is null, than the method completed successfully.
 *
 * @callback nextCallback
 * @param {?Error}      err         Optional Error object. If it is null, than assume no error has occurred.
 */


/**
 * The application callback is called upon completion of a method which is meant to deliver an application object. If the `err` parameter is null, than the method completed successfully.
 *
 * @callback appCallback
 * @param {?Error}      err         Error object. If it is null, than assume no error has occurred.
 * @param {?Object}     appObj      Application object. Will be null if an error has occurred.
 */


/**
 * The connection callback is called upon completion of a method which is meant to deliver a connection object. If the `err` parameter is null, than the method completed successfully.
 *
 * @callback connectionCallback
 * @param {?Error}      err         Error object. If it is null, than assume no error has occurred.
 * @param {?Object}     connectionObj Connection object. Will be null if an error has occurred.
 */


/**
 * The room callback is called upon completion of a method which is meant to deliver a room object. If the `err` parameter is null, than the method completed successfully.
 *
 * @callback roomCallback
 * @param {?Error}      err         Error object. If it is null, than assume no error has occurred.
 * @param {?Object}     roomObj     Room object. Will be null if an error has occurred.
 */

// Running the default listeners to initialize the events
pub.events.setDefaultListeners();