open-easyrtc
Version:
Open-EasyRTC enables quick development of WebRTC
1,224 lines (1,071 loc) • 160 kB
JavaScript
/* global module, require, console, process */
/**
* 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 2016 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 = Object.keys(e.app);
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 appName in e.app) {
if (e.app.hasOwnProperty(appName)) {
if (
e.app[appName].connection.hasOwnProperty(easyrtcid) &&
e.app[appName].connection[easyrtcid].isAuthenticated
) {
pub.app(appName, callback);
return;
}
}
}
pub.util.logWarning("Can not find connection [" + easyrtcid + "]");
callback(new pub.util.ConnectionWarning("Can not find connection [" + easyrtcid + "]"));
};
/**
* Sends the count of the number of connections to the server to a provided callback.
*
* @param {function(?Error, Number)} callback Callback with error and array containing all easyrtcids.
*/
pub.getConnectionCount = function(callback) {
callback(null, pub.getConnectionCountSync());
};
/**
* Sends the count of the number of connections to the server to a provided callback.
*
* @returns {Number} The current number of connections in a room.
*/
pub.getConnectionCountSync = function() {
var connectionCount = 0;
for (var appName in e.app) {
if (e.app.hasOwnProperty(appName)) {
connectionCount = connectionCount + _.size(e.app[appName].connection);
}
}
return connectionCount;
};
/**
* 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 appName in e.app) {
if (e.app.hasOwnProperty(appName)) {
if (
e.app[appName].connection.hasOwnProperty(easyrtcid) &&
e.app[appName].connection[easyrtcid].isAuthenticated
) {
pub.app(appName, 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 (e.option.hasOwnProperty(optionName)) {
e.option[optionName] = pub.util.deepCopy(optionValue);
return true;
} else {
pub.util.logError("Error setting option. Unrecognised option name '" + optionName + "'.");
return false;
}
};
/**
* 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 a random available easyrtcid.
*
* @function
* @return {String} Available easyrtcid. A unique identifier for an EasyRTC connection.
*/
pub.util.getAvailableEasyrtcid = eu.getAvailableEasyrtcid;
/**
* 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;
};
var errorCodesToMessages = {
"BANNED_IP_ADDR": "Client IP address is banned. Socket will be disconnected.",
"LOGIN_APP_AUTH_FAIL": "Authentication for application failed. Socket will be disconnected.",
"LOGIN_BAD_APP_NAME": "Provided application name is improper. Socket will be disconnected.",
"LOGIN_BAD_AUTH": "Authentication for application failed. Socket will be disconnected.",
"LOGIN_BAD_ROOM": "Requested room is invalid or does not exist. Socket will be disconnected.",
"LOGIN_BAD_STRUCTURE": "Authentication for application failed. The provided structure is improper. Socket will be disconnected.",
"LOGIN_BAD_USER_CFG": "Provided configuration options improper or invalid. Socket will be disconnected.",
"LOGIN_GEN_FAIL": "Authentication failed. Socket will be disconnected.",
"LOGIN_NO_SOCKETS": "No sockets available for account. Socket will be disconnected.",
"LOGIN_TIMEOUT": "Login has timed out. Socket will be disconnected.",
"MSG_REJECT_BAD_DATA": "Message rejected. The provided msgData is improper.",
"MSG_REJECT_BAD_ROOM": "Message rejected. Requested room is invalid or does not exist.",
"MSG_REJECT_BAD_FIELD": "Message rejected. Problem with field structure or name.",
"MSG_REJECT_BAD_SIZE": "Message rejected. Packet size is too large.",
"MSG_REJECT_BAD_STRUCTURE": "Message rejected. The provided structure is improper.",
"MSG_REJECT_BAD_TYPE": "Message rejected. The provided msgType is unsupported.",
"MSG_REJECT_GEN_FAIL": "Message rejected. General failure occurred.",
"MSG_REJECT_NO_AUTH": "Message rejected. Not logged in or client not authorized.",
"MSG_REJECT_NO_ROOM_LIST": "Message rejected. Room list unavailable.",
"MSG_REJECT_PRESENCE": "Message rejected. Presence could could not be set.",
"MSG_REJECT_TARGET_EASYRTCID": "Message rejected. Target easyrtcid is invalid, not using same application, or no longer online.",
"MSG_REJECT_TARGET_GROUP": "Message rejected. Target group is invalid or not defined.",
"MSG_REJECT_TARGET_ROOM": "Message rejected. Target room is invalid or not created.",
"SERVER_SHUTDOWN": "Server is being shutdown. Socket will be disconnected.",
};
/**
* 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) {
if (errorCodesToMessages.hasOwnProperty(errorCode)) {
return errorCodesToMessages[errorCode];
} else {
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;
}
/* falls through */
case "warning":
if (level === "info") {
break;
}
/* falls through */
case "info":
if (level === "debug") {
break;
}
/* falls through */
case "debug":
pub.events.emit("log", level, logText, logFields);
break;
}
};
/**
* 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);
};
/**
* Sends an 'ack' socket message to a given socketCallback. Provides additional checking and logging.
*
* @param {string} easyrtcid EasyRTC unique identifier for a socket connection.
* @param {Function} socketCallback Socket.io callback function
* @param {?Object} appObj EasyRTC application object. Contains methods used for identifying and managing an application.
*/
pub.util.sendSocketCallbackAck = function(easyrtcid, socketCallback, appObj) {
return pub.util.sendSocketCallbackMsg(easyrtcid, socketCallback, {"msgType":"ack"}, appObj);
};
/**
* Sends a complete socket message to a given socketCallback. Provides additional checking and logging.
*
* @param {string} easyrtcid EasyRTC unique identifier for a socket connection.
* @param {Function} socketCallback Socket.io callback function
* @param {Object} msg Message object which contains the full message for 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.
*/
pub.util.sendSocketCallbackMsg = function(easyrtcid, socketCallback, msg, appObj) {
var appName;
if (appObj) {
appName = appObj.getAppName();
if (!appObj.isConnectedSync(easyrtcid)) {
pub.util.logDebug("["+appName+"]["+easyrtcid+"] Unable to return socket message. Peer no longer connected.");
return false;
}
}
if (!_.isFunction(socketCallback)) {
pub.util.logWarning("["+appName+"]["+easyrtcid+"] Unable to return socket message. Provided socketCallback was not a function.");
return false;
}
try {
socketCallback(msg);
} catch(err) {
pub.util.logWarning("["+appName+"]["+easyrtcid+"] Unable to return socket message. Call to socketCallback failed.");
}
if (e.option.logMessagesEnable) {
try {
pub.util.logDebug("["+appName+"]["+easyrtcid+"] Returning socket.io message: ["+JSON.stringify(msg)+"]");
}
catch(err) {
pub.util.logDebug("["+appName+"]["+easyrtcid+"] Returning socket.io message");
}
}
return true;
};
/**
* 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 (msg.msgData.roomJoin.hasOwnProperty(currentRoomName)) {
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; /* jshint ignore:line */
break; /* jshint ignore:line */
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 joinRoomName in msg.msgData.roomJoin) {
if (msg.msgData.roomJoin.hasOwnProperty(joinRoomName)) {
if (
!getOption("roomNameRegExp").test(joinRoomName) ||
!_.isObject(msg.msgData.roomJoin[joinRoomName]) ||
!_.isString(msg.msgData.roomJoin[joinRoomName].roomName) ||
joinRoomName !== msg.msgData.roomJoin[joinRoomName].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 leaveRoomName in msg.msgData.roomLeave) {
if (msg.msgData.roomLeave.hasOwnProperty(leaveRoomName)) {
if (
!getOption("roomNameRegExp").test(leaveRoomName) ||
!_.isObject(msg.msgData.roomLeave[leaveRoomName]) ||
!_.isString(msg.msgData.roomLeave[leaveRoomName].roomName) ||
leaveRoomName !== msg.msgData.roomLeave[leaveRoomName].roomName) {
callback(null, false, "MSG_REJECT_BAD_STRUCTURE");
return;
}
}
}
break;
case "stillAlive":
// Deprecated
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.cookieseasyrtcsid !== 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) {
if (appDefaultFieldObj.hasOwnProperty(currentFieldName)) {
appObj.setField(
currentFieldName,
appDefaultFieldObj[currentFieldName].fieldValue,
appDefaultFieldObj[currentFieldName].fieldOption,
null
);
}
}
}
if (appObj.getOption("roomDefaultEnable")) {
pub.events.emit("roomCreate", appObj, null, 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;
};
/**
* Sends the count of the number of connections in the app to a provided callback.
*
* @memberof pub.appObj
* @param {function(?Error, Number)} callback Callback with error and array containing all easyrtcids.
*/
appObj.getConnectionCount = function(callback) {
callback(null, appObj.getConnectionCountSync());
};
/**
* Sends the count of the number of connections in the app to a provided callback.
*
* @memberof pub.appObj
* @returns {Number} The current number of connections in a room.
*/
appObj.getConnectionCountSync = function() {
return _.size(e.app[appName].connection);
};
/**
* 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) {