UNPKG

zdpjs_webrtc

Version:
1,279 lines (1,118 loc) 158 kB
let events = require("events"); let async = require("async"); let _ = require("underscore"); // 外部模块通用实用函数 let g = require("./general_util"); // 通用实用函数本地模块 let e = require("./easyrtc_private_obj"); // EasyRTC 私有对象 let eventListener = require("./easyrtc_default_event_listeners"); // EasyRTC 默认事件监听器 let eu = require("./easyrtc_util"); // EasyRTC 工具类函数 /** * EasyRTC listen()函数返回的公共对象。包含与EasyRTC服务器交互的所有公共方法。 * * @class */ let pub = module.exports; /** * Socket.io 服务对象的别名. 在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; /** * Express对象的别名,在Listen()期间设置 * * @member {Object} pub.httpApp */ pub.httpApp = null; /** * 将所有应用程序名称的数组发送给回调。 * * @param {function(Error, Array.<string>)} callback 有错误的回调和包含所有应用程序名称的数组。 */ pub.getAppNames = function (callback) { let appNames = Object.keys(e.app); callback(null, appNames); }; /** * 获取应用程序的应用程序对象,该应用程序具有具有给定easyrtcid的经过身份验证的客户端 * * @param {String} easyrtcid EasyRTC连接的唯一标识符 * @param {function(?Error, Object=)} callback 回调函数 */ pub.getAppWithEasyrtcid = function (easyrtcid, callback) { for (let 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("找不到连接 [" + easyrtcid + "]"); callback(new pub.util.ConnectionWarning("找不到连接 [" + easyrtcid + "]")); }; /** * 通过回调函数获取连接个数 * * @param {function(?Error, Number)} callback 返回所有easyrtcid的数组和错误信息 */ pub.getConnectionCount = function (callback) { callback(null, pub.getConnectionCountSync()); }; /** * 异步获取连接个数 * * @returns {Number} 当前房间中的连接数 */ pub.getConnectionCountSync = function () { let connectionCount = 0; for (let appName in e.app) { if (e.app.hasOwnProperty(appName)) { connectionCount = connectionCount + _.size(e.app[appName].connection); } } return connectionCount; }; /** * 根据easyrtcid获取连接对象 * * @param {string} easyrtcid EasyRTC 唯一标识符 * @param {function(?Error, Object=)} callback 回调函数 */ pub.getConnectionWithEasyrtcid = function (easyrtcid, callback) { for (let 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("找到该连接[" + easyrtcid + "]"); callback(new pub.util.ConnectionWarning("找不到该连接[" + easyrtcid + "]")); }; /** * 获取配置 * * 注意,可以在应用程序或房间级别设置一些选项。如果一个选项在房间级别没有被设置,它将检查它是否在应用程序级别被设置,如果没有,它将恢复到服务器级别。 * * @param {String} optionName 配置名称 * @return {*} 配置值 */ 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]; }; /** * 获取EasyRTC版本号 * * @return {string} EasyRTC版本 */ pub.getVersion = function () { return e.version; }; /** * 获取EasyRTC私有对象 * * @private * @return {Object} EasyRTC 私有对象 */ pub._getPrivateObj = function () { return e; }; /** * 配置参数,用于调整服务器级别 * * 注意,可以在应用程序或房间级别设置一些选项。如果一个选项在房间级别没有被设置,它将检查它是否在应用程序级别被设置,如果没有,它将恢复到服务器级别。 * * @param {Object} optionName 配置名 * @param {Object} optionValue 配置值 * @return {Boolean} 成功返回true,失败返回false */ pub.setOption = function (optionName, optionValue) { // 只能设置当前存在的选项 if (e.option.hasOwnProperty(optionName)) { e.option[optionName] = pub.util.deepCopy(optionValue); return true; } else { pub.util.logError("错误的设置选项。未被发现的选项名称 '" + optionName + "'."); return false; } }; /** * EasyRTC事件列表 * @class */ pub.events = {}; /** * EasyRTC事件提交 * * @private */ pub.events._eventListener = new events.EventEmitter(); /** * 公开事件提交方法 * * @param {string} eventName EasyRTC事件名称 * @param {...*} eventParam 事件参数 */ pub.events.emit = pub.events._eventListener.emit.bind(pub.events._eventListener); /** * 为给定的事件运行默认的EasyRTC监听器。 * * @param {string} eventName EasyRTC事件名称 * @param {...*} eventParam 事件参数 */ pub.events.emitDefault = function () { if (!pub.events.defaultListeners[arguments['0']]) { console.error("错误发出侦听器。 事件参数 '" + arguments['0'] + "' 不存在。"); return; } pub.events.defaultListeners[Array.prototype.shift.call(arguments)].apply(this, arguments); }; /** * 重置默认的监听器 * * @param {string} eventName EasyRTC 事件名称 */ pub.events.setDefaultListener = function (eventName) { if (!_.isFunction(pub.events.defaultListeners[eventName])) { console.error("设置默认监听器错误。 不存在事件 '" + eventName + "' 的默认值。 "); } pub.events._eventListener.removeAllListeners(eventName); pub.events._eventListener.on(eventName, pub.events.defaultListeners[eventName]); }; /** * */ pub.events.setDefaultListeners = function () { pub.events._eventListener.removeAllListeners(); for (let 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."); } } }; /** * EasyRTC事件监听器名称到其默认函数的映射。 此映射可用于手动运行默认函数。 */ 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 letiable (or object) to be copied. * @returns {Object} New copy of letiable. */ 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) { let 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; }; let 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) { let 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 letiable which points to the getOption function at either the top or application level let 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; } let 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 (let 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 (let 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 (let 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": 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 let appDefaultFieldObj = appObj.getOption("appDefaultFieldObj"); if (_.isObject(appDefaultFieldObj)) { for (let 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 */ let 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) { let easyrtcids = Object.keys(e.app[appName].connection); callback(null, easyrtcids); }; /** * Returns an array of all easyrtcids connected to the application associated with a given username * * @memberof pub.appObj * @param {string} username Username to search for. * @param {function(?Error, Array.<string>)} callback Callback with error and array of easyrtcids. */ appObj.getConnectedEasyrtcidsWithUsername = function (username, callback) { let easyrtcids = []; for (let currentEasyrtcid in e.app[appName].connection) { if (!e.app[appName].connection.hasOwnProperty(currentEasyrtcid)) { continue; } if (e.app[appName].connection[currentEasyrtcid].username === username) { easyrtcids.push(currentEasyrtcid); } } callback(null, easyrtcids); }; /** * Returns an array of all easyrtcids connected to the application associated with a given username * * @memberof pub.appObj * @param {string} username Username to search for. * @returns {Array.<string>} array of easyrtcids. */ appObj.getConnectedEasyrtcidsWithUsernameSync = function (username) { let easyrtcids = []; for (let currentEasyrtcid in e.app[appName].connection) { if (!e.app[appName].connection.hasOwnProperty(currentEasyrtcid)) { continue; } if (e.app[appName].connection[currentEasyrtcid].username === username) { easyrtcids.push(currentEasyrtcid); } } return 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