/**
* 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 2014 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 = [];
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} optionName Option name
* @return {*} Option value (can be any JSON type)
*/
pub.getOption = function(optionName) {
if(typeof e.option[optionName] === "undefined"){
pub.util.logError("Unknown option requested. Unrecognised option name '" + optionName + "'.");
return null;
}
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} optionName Option name
* @param {Object} optionValue 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 EventEmitter.
*
* @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 {Error} err Error object
*/
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.
*
* @extends Error
* @param {string} msg Text message describing the error.
* @returns {Error}
*/
pub.util.ApplicationError = eu.ApplicationError;
/**
* Custom Error Object for EasyRTC Application Warnings.
*
* @extends Error
* @param {string} msg Text message describing the error.
* @returns {Error}
*/
pub.util.ApplicationWarning = eu.ApplicationWarning;
/**
* Custom Error Object for EasyRTC C Errors.
*
* @function
* @extends Error
* @param {string} msg Text message describing the error.
* @returns {Error}
*/
pub.util.ConnectionError = eu.ConnectionError;
/**
* Custom Error Object for EasyRTC Connection Warnings.
*
* @function
* @extends Error
* @param {string} msg Text message describing the error.
* @returns {Error}
*/
pub.util.ConnectionWarning = eu.ConnectionWarning;
/**
* Custom Error Object for EasyRTC Server Errors.
*
* @function
* @extends Error
* @param {string} msg Text message describing the error.
* @returns {Error}
*/
pub.util.ServerError = eu.ServerError;
/**
* Custom Error Object for EasyRTC Server Warnings.
*
* @function
* @extends Error
* @param {string} msg Text message describing the error.
* @returns {Error}
*/
pub.util.ServerWarning = eu.ServerWarning;
/**
* Returns an EasyRTC message error object for a specific error code. This is meant to be emitted 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 occurred.";
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/ for details or run 'npm update' to upgrade.");
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/ for details.");
}
});
}).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;
}
// if roomParameter field is defined, it must be an object
if (msg.msgData.roomJoin[currentRoomName].roomParameter !== undefined && !_.isObject(msg.msgData.roomJoin[currentRoomName].roomParameter)) {
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;
}
}
else {
// Incoming message syntactically valid
callback(null, true, null);
}
}
);
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 each key is an easyrtcid
// 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 session id 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.ApplicationError("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.logDebug("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 easyrtcids connected to the application
*
* @memberof pub.appObj
* @param {function(?Error, Array.<string>)} callback Callback with error and array of easyrtcids.
*/
appObj.getConnectionEasyrtcids = function(callback) {
var easyrtcids = [];
for (var key in e.app[appName].connection) {
easyrtcids.push(key);
}
callback(null, easyrtcids);
};
/**
* Returns application level field object for a given field name to a provided callback.
*
* @memberof pub.appObj
* @param {string} fieldName Field name
* @param {function(?Error, Object=)} callback Callback with error and field object (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 application level field object for a given field name. If the field is not set, it will return a field object will a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj
* @param {string} fieldName Field name
* @returns {Object} Field object
*/
appObj.getFieldSync = function(fieldName) {
if (!e.app[appName].field[fieldName]) {
return {"fieldName": fieldName, "fieldOption": {}, "fieldValue": null};
}
return pub.util.deepCopy(e.app[appName].field[fieldName]);
};
/**
* Returns application level field value for a given field name. If the field is not set, it will return a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj
* @param {string} fieldName Field name
* @returns {?*} Field value. Can be any JSON object.
*/
appObj.getFieldValueSync = function(fieldName) {
if (!e.app[appName].field[fieldName]) {
return null;
}
return pub.util.deepCopy(e.app[appName].field[fieldName].fieldValue);
};
/**
* 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 = [];
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} optionName Option name
* @return {*} Option value (can be any JSON 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 = [];
for (var key in e.app[appName].room) {
roomNames.push(key);
}
callback(null, roomNames);
};
/**
* Returns an array of all easyrtcsids within the application
*
* @memberof pub.appObj
* @param {function(?Error, Array.<string>)} callback Callback with error and array containing easyrtcsids.
*/
appObj.getEasyrtcsids = function(callback) {
var easyrtcsids = [];
for (var key in e.app[appName].session) {
easyrtcsids.push(key);
}
callback(null, easyrtcsids);
};
/**
* Returns an array of all easyrtcsids 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. See http://socket.io/ for more information.
*
* @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 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 to a provided callback.
*
* @memberof pub.appObj.connectionObj
* @param {string} fieldName Field name
* @param {function(?Error, Object=)} callback Callback with error and field object (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 connection level field object for a given field name. If the field is not set, it will return a field object will a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj.connectionObj
* @param {string} fieldName Field name
* @returns {Object} Field object
*/
connectionObj.getFieldSync = function(fieldName) {
if (!e.app[appName].connection[easyrtcid].field[fieldName]) {
return {"fieldName": fieldName, "fieldOption": {}, "fieldValue": null};
}
return pub.util.deepCopy(e.app[appName].connection[easyrtcid].field[fieldName]);
};
/**
* Returns connection level field value for a given field name. If the field is not set, it will return a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj.connectionObj
* @param {string} fieldName Field name
* @returns {?*} Field value
*/
connectionObj.getFieldValueSync = function(fieldName) {
if (!e.app[appName].connection[easyrtcid].field[fieldName]) {
return null;
}
return pub.util.deepCopy(e.app[appName].connection[easyrtcid].field[fieldName].fieldValue);
};
/**
* Returns an object containing all field names and values within the connection to a provided callback. 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 = [];
for (var key in e.app[appName].connection[easyrtcid].room) {
roomNames.push(key);
}
callback(null, roomNames);
};
/**
* 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;
};
/**
* 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);
}
};
/**
* Returns the username associated with the connection. Returns NULL if no username has been set.
* Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
*
* @memberof pub.appObj.connectionObj
* @return {String} The username associated with the connection.
*/
connectionObj.getUsername = function() {
return e.app[appName].connection[easyrtcid].username;
};
/**
* Joins the connection to a specified session. A connection can only be assigned to one session.
*
* @memberof pub.appObj.connectionObj
* @param {string} easyrtcsid EasyRTC session identifier
* @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;
}
if(!e.app[appName] || !e.app[appName].connection[easyrtcid] || !e.app[appName].session[easyrtcsid]) {
next(new pub.util.ConnectionWarning("[" + appName + "][" + easyrtcid + "] Session [" + easyrtcsid + "] does not exist. Could not join session"));
return;
}
e.app[appName].connection[easyrtcid].toSession = e.app[appName].session[easyrtcsid];
e.app[appName].connection[easyrtcid].toSession.toConnection[easyrtcid] = e.app[appName].connection[easyrtcid];
// 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) {
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 JSON 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 listDeltas 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 {function(?Error, 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];
}
}
// Anonymous wrapper to deliver arguments
(function(innerCurrentEasyrtcid, innerMsg){
connectionObj.getApp().connection(innerCurrentEasyrtcid, function(err, emitToConnectionObj) {
if (!err && innerCurrentEasyrtcid != easyrtcid && emitToConnectionObj) {
pub.events.emit("emitEasyrtcCmd", emitToConnectionObj, "roomData", innerMsg, null, function() {});
}
});
})(currentEasyrtcid, msg);
}
});
};
/**
* 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 {function(?Error, 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 {function(?Error, 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() {
if (e.app[appName].connection[easyrtcid] && e.app[appName].connection[easyrtcid].isAuthenticated) {
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() {
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 connectionRoom object.
*
* @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 room doesn't exist
if (!appObj.isRoomSync(roomName)) {
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;
}
// 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: {},
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);
};
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;
};
/**
* Returns the room name 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 room name
*/
connectionRoomObj.getRoomName = function() {
return roomName;
};
/**
* 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;
}
if (appObj.isRoomSync(roomName)){
e.app[appName].room[roomName].modifiedOn = Date.now();
delete e.app[appName].room[roomName].clientList[easyrtcid];
}
if (e.app[appName].connection[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?
* @param {function(?Error, Object=)} callback Callback with error and room data delta object.
*/
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;
}
if (!appObj.isRoomSync(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;
}
var msg = {"msgData": {"roomData": {}}};
msg.msgData.roomData[roomName] = roomDataDelta;
for (var currentEasyrtcid in e.app[appName].room[roomName].clientList) {
// Anonymous wrapper to deliver arguments
(function(innerCurrentEasyrtcid, innerMsg){
connectionObj.getApp().connection(innerCurrentEasyrtcid, function(err, emitToConnectionObj) {
if (!err && innerCurrentEasyrtcid != easyrtcid && emitToConnectionObj) {
pub.events.emit("emitEasyrtcCmd", emitToConnectionObj, "roomData", innerMsg, null, function() {
});
}
});
})(currentEasyrtcid, msg);
}
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?
* @param {function(?Error, Object=)} callback Callback with error and room data delta object.
*/
connectionRoomObj.generateRoomDataDelta = function(isLeavingRoom, callback) {
pub.util.logDebug("[" + appName + "][" + easyrtcid + "] Room [" + roomName + "] Running func 'connectionRoomObj.generateRoomDataDelta'");
if (!_.isFunction(callback)) {
callback = pub.util.nextToNowhere;
}
if (!appObj.isRoomSync(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;
}
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 {object} apiFieldObj A API field object, including the field name and field value.
* @param {nextCallback} next A success callback of form next(err).
*/
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 connectionRoomObj
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;
// Remove link to connection from session in local storage
if (e.app[appName].connection[easyrtcid].toSession) {
delete e.app[appName].connection[easyrtcid].toSession.toConnection[easyrtcid];
}
// Remove connection from local storage
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);
});
};
/**
* Counts how many occupants are in a room.
*
* @memberof pub.appObj
* @param {string} roomName Room name which uniquely identifies a room within an EasyRTC application.
* @param {function(?Error, number=)} callback Callback with error and client count
*/
appObj.getRoomOccupantCount = function(roomName, callback) {
if (!appObj.isRoomSync(roomName)) {
callback(new pub.util.ApplicationWarning("Attempt to request non-existent room name: '" + roomName + "'"));
return;
}
callback(null, _.size(e.app[appName].room[roomName].clientList));
};
/**
* Delete an existing room, providing the room is empty.
*
* @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 true if a room was deleted.
*/
appObj.deleteRoom = function(roomName, callback) {
if (!roomName) {
var errorMsg = "Can't delete room with a null room name";
pub.util.logWarning(errorMsg);
callback(new pub.util.ApplicationWarning(errorMsg), false);
return;
}
// If room is already deleted or if it doesn't exist, then report success
if (!appObj.isRoomSync(roomName)) {
callback(null, true);
return;
}
if (!_.isEmpty(e.app[appName].room[roomName].clientList)){
var errorMsg = "Can't delete room " + roomName + " because it isn't empty";
pub.util.logWarning(errorMsg);
callback(new pub.util.ApplicationWarning(errorMsg), false);
return;
}
e.app[appName].room[roomName].deleted = true;
delete e.app[appName].room[roomName];
callback(null, true);
};
/**
* 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 (appObj.isRoomSync(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,
deleted: false,
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.ApplicationError("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(),
"toConnection":{},
"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] && e.app[appName].room[roomName] && !e.app[appName].room[roomName].deleted) ? true : false));
};
/**
* Checks if a provided room is defined. This is a synchronous function, thus may not be available in custom cases where room state is not kept in memory.
*
* @memberof pub.appObj
* @param {string} roomName Room name which uniquely identifies a room within an EasyRTC application.
* @return {Boolean} Returns boolean. True if room is defined.
*/
appObj.isRoomSync = function(roomName) {
return ((e.app[appName] && e.app[appName].room[roomName] && !e.app[appName].room[roomName].deleted) ? true : false);
};
/**
* Checks if a provided session is defined. The callback returns a boolean if session is defined
*
* @memberof pub.appObj
* @param {string} easyrtcsid EasyRTC session identifier
* @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} groupName 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 easyrtcids.
*/
groupObj.getConnections = function(callback) {
var connectedEasyrtcidArray = [];
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 (!appObj.isRoomSync(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 name
*/
roomObj.getAppName = function() {
return appName;
};
/**
* Returns the room name for the current room. Note that unlike most EasyRTC functions, this returns a value and does not use a callback.
*
* @memberof pub.appObj.roomObj
* @return {string} The room name
*/
roomObj.getRoomName = function() {
return roomName;
};
/**
* 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;
}
if (!appObj.isRoomSync(roomName)) {
pub.util.logWarning("Attempt to request non-existent room name: '" + roomName + "'");
next(new pub.util.ApplicationWarning("Attempt to request non-existent room name: '" + roomName + "'"));
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) {
if (err || !_.isObject(targetConnectionObj)) {
pub.util.logDebug("[" + currentEasyrtcid + "] Could not get connection object to send room data field update. Client may have disconnected.");
asyncCallback(null);
return;
}
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 to a provided callback.
*
* @memberof pub.appObj.roomObj
* @param {string} fieldName Field name
* @param {function(?Error, Object=)} callback Callback with error and field object (any type)
*/
roomObj.getField = function(fieldName, callback) {
if (!appObj.isRoomSync(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;
}
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 room level field object for a given field name. If the field is not set, it will return a field value will a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj.roomObj
* @param {string} fieldName Field name
* @returns {Object} Field object
*/
roomObj.getFieldSync = function(fieldName) {
if (!appObj.isRoomSync(roomName)) {
return {"fieldName": fieldName, "fieldOption": {}, "fieldValue": null};
}
if (!e.app[appName].room[roomName].field[fieldName]) {
return {"fieldName": fieldName, "fieldOption": {}, "fieldValue": null};
}
return pub.util.deepCopy(e.app[appName].room[roomName].field[fieldName]);
};
/**
* Returns room level field value for a given field name. If the field is not set, it will return a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj.roomObj
* @param {string} fieldName Field name
* @returns {?*} Field value
*/
roomObj.getFieldValueSync = function(fieldName) {
if (!appObj.isRoomSync(roomName)) {
return null;
}
if (!e.app[appName].room[roomName].field[fieldName]) {
return null;
}
return pub.util.deepCopy(e.app[appName].room[roomName].field[fieldName].fieldValue);
};
/**
* 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) {
if (!appObj.isRoomSync(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;
}
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} optionName Option name
* @return {*} Option value (can be any type)
*/
roomObj.getOption = function(optionName) {
return ((!appObj.isRoomSync(roomName) || 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} optionName Option name
* @param {Object} optionValue Option value
* @return {Boolean} true on success, false on failure
*/
roomObj.setOption = function(optionName, optionValue) {
if (!appObj.isRoomSync(roomName)) {
pub.util.logWarning("Attempt to request non-existent room name: '" + roomName + "'");
return false;
}
// 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;
};
/**
* Incomplete function for setting an easyrtcid as being a client in a room.
*
* @memberof pub.appObj.roomObj
* @param {string} easyrtcid EasyRTC unique identifier for a socket connection.
* @param {nextCallback} next A success callback of form next(err).
* @ignore
*/
roomObj.setConnection = function(easyrtcid, next) {
if (!appObj.isRoomSync(roomName)) {
pub.util.logWarning("Attempt to request non-existent room name: '" + roomName + "'");
next(new pub.util.ApplicationWarning("Attempt to request non-existent room name: '" + roomName + "'"));
return;
}
pub.util.logWarning("Using deprecated roomObj.setConnection() function");
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) {
if (!appObj.isRoomSync(roomName)) {
pub.util.logWarning("Attempt to request non-existent room name: '" + roomName + "'");
next(new pub.util.ApplicationWarning("Attempt to request non-existent room name: '" + roomName + "'"));
return;
}
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 easyrtcids of all connected clients within the room.
*
* @memberof pub.appObj.roomObj
* @param {function(?Error, Array.<string>=)} callback Callback with error and array containing all easyrtcids.
*/
roomObj.getConnections = function(callback) {
if (!appObj.isRoomSync(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;
}
var connectedEasyrtcidArray = [];
for (var key in e.app[appName].room[roomName].clientList) {
connectedEasyrtcidArray.push(key);
}
callback(null, connectedEasyrtcidArray);
};
/**
* Returns the connectionObj for a given easyrtcid, but only if it is currently a client in the room
*
* @memberof pub.appObj.roomObj
* @param {string} easyrtcid EasyRTC unique identifier for a socket connection.
* @param {function(?Error, Object=)} callback Callback with error and connectionObj.
*/
roomObj.getConnectionWithEasyrtcid = function(easyrtcid, callback) {
if (!appObj.isRoomSync(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;
}
if (e.app[appName].room[roomName].clientList[easyrtcid]){
appObj.connection(easyrtcid, function(err, connectionObj) {
if (err) {
callback(new pub.util.ConnectionWarning("Can not find connection [" + easyrtcid + "] in room."));
return;
}
// If there is no error, than run callback with the connection object.
callback(null, connectionObj);
});
}
else {
callback(new pub.util.ConnectionWarning("Can not find connection [" + easyrtcid + "] in room."));
}
};
/**
* Returns an array containing the connectionObjs of all connected clients within the room.
*
* @memberof pub.appObj.roomObj
* @param {function(?Error, Array.<Object>=)} callback Callback with error and array containing connectionObjs.
*/
roomObj.getConnectionObjects = function(callback) {
if (!appObj.isRoomSync(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;
}
var connectedObjArray = [];
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);
asyncCallback(null);
});
},
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 EasyRTC session identifier
* @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 to a provided callback.
*
* @memberof pub.appObj.sessionObj
* @param {string} fieldName 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 session level field object for a given field name. If the field is not set, it will return a field object will a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj.sessionObj
* @param {string} fieldName Field name
* @returns {Object} Field object
*/
sessionObj.getFieldSync = function(fieldName) {
if (!e.app[appName].session[easyrtcsid].field[fieldName]) {
return {"fieldName": fieldName, "fieldOption": {}, "fieldValue": null};
}
return pub.util.deepCopy(e.app[appName].session[easyrtcsid].field[fieldName]);
};
/**
* Returns session level field value for a given field name. If the field is not set, it will return a null field value. This is a synchronous function, thus may not be available in custom cases where state is not kept in memory.
*
* @memberof pub.appObj.sessionObj
* @param {string} fieldName Field name
* @returns {?*} Field value
*/
sessionObj.getFieldValueSync = function(fieldName) {
if (!e.app[appName].session[easyrtcsid].field[fieldName]) {
return null;
}
return pub.util.deepCopy(e.app[appName].session[easyrtcsid].field[fieldName].fieldValue);
};
/**
* Returns an object containing all field names and values within the session to a provided callback. 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);
};
sessionObj.emitSessionDataFieldUpdate = function(next) {
sessionObj.getFields(true, function(err, fieldObj) {
if (err) {
next(err);
return;
}
var outgoingMsg = {"msgData": {"sessionData": {}}};
outgoingMsg.msgData.sessionData = {
"easyrtcsid": easyrtcsid,
"sessionStatus": "update"
};
outgoingMsg.msgData.sessionData.field = fieldObj;
// Loop through all active connection objects belonging to session
async.each(
Object.keys(e.app[appName].session[easyrtcsid].toConnection),
function(currentEasyrtcid, asyncCallback) {
// Retrieve a connection object, then send the sessionData message.
appObj.connection(currentEasyrtcid, function(err, targetConnectionObj) {
if (err || !_.isObject(targetConnectionObj)) {
pub.util.logDebug("[" + currentEasyrtcid + "] Could not get connection object to send session data field update. Client may have disconnected.");
asyncCallback(null);
return;
}
// Emit sessionData easyrtcCmd to each connection
pub.events.emit("emitEasyrtcCmd", targetConnectionObj, "sessionData", outgoingMsg, function(msg) {
}, function(err) {
// Ignore errors if unable to send to a socket.
asyncCallback(null);
});
});
},
function(err) {
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.
*/
// Documenting Custom Type-Definitions
/**
* An error object
*
* @typedef {Object} Error
*/
// Running the default listeners to initialize the events
pub.events.setDefaultListeners();