chronosjs
Version:
JS Channels Mechanism
1,316 lines (1,205 loc) • 177 kB
JavaScript
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("Chronos.EventsUtil", [], function () {
return factory(root, root, true);
});
return;
}
//</amd>
/* istanbul ignore next */
if ("object" === typeof exports) {
// CommonJS
factory(root, exports);
}
/* istanbul ignore next */
else {
// Browser globals
root.Chronos = root.Chronos || {};
factory(root, root.Chronos);
}
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, hide) {
"use strict";
function getListeners(lstnrs, eventName, appName) {
var callBacks = [];
if (lstnrs[eventName] && lstnrs[eventName].length) {
for (var i = 0; i < lstnrs[eventName].length; i++) {
if ((!appName || "*" === lstnrs[eventName][i].appName) ||//generic event // &&
lstnrs[eventName][i].appName === appName) {//Specific events for a named instance
callBacks.push(lstnrs[eventName][i]);
}
}
}
if (lstnrs["*"]) {
for (var k = 0; k < lstnrs["*"].length; k++) {
if ((!appName || "*" === lstnrs["*"][k].appName) ||//generic event // &&
lstnrs["*"][k].appName === appName) {//Specific events for a named instance
callBacks.push(lstnrs["*"][k]);
}
}
}
return callBacks;
}
/* istanbul ignore next */
function log(msg, level, app) {
if (root && "function" === typeof root.log) {
root.log(msg, level, app);
}
}
/**
* var eventObj = {
* unbindObj: unbindObj,
* attrName: "eventName",
* loggerName: "Events",
* lstnrs: {}
* };
*/
function unbind(eventObj) {
var cmdName = eventObj.unbindObj[eventObj.attrName];
var unBound = false;
var updatedListeners;
if (!eventObj.unbindObj) {
log("CMD listen id not spec for unbind", "ERROR", eventObj.loggerName);
return null;
}
if (typeof eventObj.unbindObj === "string") {//Data is of type commandId
return _unregister(eventObj.lstnrs, eventObj.unbindObj);
}
else if (!eventObj.unbindObj.func && !eventObj.unbindObj.context && !eventObj.unbindObj.appName) {//No data passed in to help us find unbind
return false;
}
var listeners = eventObj.lstnrs;
if (cmdName) {
listeners = {};
listeners[cmdName] = eventObj.lstnrs[cmdName];
}
for (var key in listeners) {
if (listeners.hasOwnProperty(key)) {
if (listeners[key] && listeners[key].length) {
updatedListeners = _unbind(listeners[key], eventObj.unbindObj.func, eventObj.unbindObj.context, eventObj.unbindObj.appName);
if (updatedListeners.length !== listeners[key].length) {
eventObj.lstnrs[key] = updatedListeners;
unBound = true;
}
}
}
}
return unBound;
}
/**
* Clones objects and properties (everything except functions)
* @param cloneObj - the object we want to clone
* @return {Object}
*/
function cloneEventData(cloneObj) {
var resultObj = {};
if (cloneObj.constructor === Object) {//If this is an object
for (var key in cloneObj) {
//noinspection JSUnfilteredForInLoop
if (cloneObj.hasOwnProperty(key) && cloneObj[key] !== null && cloneObj[key] !== undefined) {//Make sure we have some data that's object specific
//noinspection JSUnfilteredForInLoop
if (typeof cloneObj[key] === "object" && cloneObj[key].constructor !== Array) {
//noinspection JSUnfilteredForInLoop
resultObj[key] = cloneEventData(cloneObj[key]);
}
else { //noinspection JSUnfilteredForInLoop
if (cloneObj[key].constructor === Array) {
//noinspection JSUnfilteredForInLoop
resultObj[key] = cloneObj[key].slice(0) || [];
}
else { //noinspection JSUnfilteredForInLoop
if (typeof cloneObj[key] !== "function") {
//noinspection JSUnfilteredForInLoop
resultObj[key] = cloneObj[key] !== null && cloneObj[key] !== undefined ? cloneObj[key] : "";
}
}
}
}
}
} else {//Return copy of Array or primitive type in case of no Object
if (cloneObj.constructor === Array) {
resultObj = cloneObj.slice(0) || [];
}
else if (typeof cloneObj !== "function") {
resultObj = cloneObj;
}
}
return resultObj;
}
function hasFired(fired, app, evName) {
if ((typeof (evName) === "undefined" || evName === "*") && app === "*") {
return fired;
}
var firedEvents = [];
for (var n = 0; n < fired.length; n++) {
if (fired[n].eventName === evName || evName === "*") {
if ((app && app === fired[n].appName) ||//For events specific to a caller
(!fired[n].appName || fired[n].appName === "*") || app === "*") { //For all events that don't have a specific appName
firedEvents.push(fired[n]);
}
}
}
return firedEvents;
}
/**
* Stores events so we can later ask for them, can be set to a limited store by defaults on instantiation
* @param data = {
* triggerData: triggerData,
* eventBufferLimit: eventBufferLimit,
* attrName: attrName,
* fired: fired,
* index: index
* }
*/
function storeEventData(data) {
//noinspection JSUnresolvedVariable
if (data.eventBufferLimit === 0 || (data.triggerData.data && !!data.triggerData.data.doNotStore)) {//No events should be stored or this event should not be stored
data = null;
return;
}
var firedEventData = {eventName: data.triggerData[data.attrName], appName: data.triggerData.appName};
firedEventData.data = data.triggerData.passDataByRef ? data.triggerData.data : cloneEventData(data.triggerData.data);
if (data.eventBufferLimit > 0) {//Limiting Array size to what was decided on creation
if (data.index >= data.eventBufferLimit) {
data.index = 0;
}
data.fired[data.index] = firedEventData;
data.index++;
}
else {//All events should be stored
data.fired.push(firedEventData);
}
data = null;
}
function _unregister(lstnrs, eventId) {
var unBound = false;
if (!eventId) {
log("Ev listen id not spec for unregister", "ERROR", "Events");
return null;
}
for (var eventName in lstnrs) {
if (lstnrs.hasOwnProperty(eventName)) {
for (var i = 0; i < lstnrs[eventName].length; i++) {
if (lstnrs[eventName][i].id == eventId) {
lstnrs[eventName].splice(i, 1);
log("Ev listen=" + eventId + " and name=" + eventName + " unregister", "DEBUG", "Events");
unBound = true;
break;
}
}
}
}
if (!unBound) {
log("Ev listen not found " + eventId + " unregister", "DEBUG", "Events");
}
return unBound;
}
/**
* The actual unbinding of the callbacks from the events mechanism
* @param listeners - the array of listeners that match this query
* @param func - the function we want to unbind
* @param context - the context we want to unbind
* @param appName - the specific appName we want to unbind (UID)
* @return {Array} - the new array of listeners we want to use
*/
function _unbind(listeners, func, context, appName) {
var newListeners = [];
if (listeners && listeners.length) {
for (var i = 0; i < listeners.length; i++) {
try {
var sameFunc = (!context && listeners[i].func === func);//If this fits the function and no context was passed
var sameContext = (!func && context && listeners[i].context === context);//If this fits the context and no function was passed
var sameFuncAndContext = (func && context && listeners[i].func === func && listeners[i].context === context);//if this fits the function and context
var hasSameAppName = (appName && appName === listeners[i].appName);//If we're unbinding a specific appName
var hasGlobalAppName = (listeners[i].appName === "*");//If we're unbinding a general appName
if ((sameFunc || sameContext || sameFuncAndContext)) {
if (hasSameAppName || hasGlobalAppName) {
continue;//This is a callback to remove
}
if (sameContext) {
continue;
}
}
else if (!func && !context && hasSameAppName) {//This should only happen if nothing but an appName was passed in
continue;//This is a callback to remove
}
newListeners.push(listeners[i]);//This is callbacks we keep
} catch (err) {
log("Error in unbind e=" + err.message, "ERROR", "Events");
}
}
}
return newListeners;
}
// attach properties to the exports object to define
// the exported module properties.
var ret = {
getListeners: getListeners,
log: log,
unbind: unbind,
hasFired: hasFired,
cloneEventData: cloneEventData,
storeEventData: storeEventData
};
if (!hide) {
exports.EventsUtil = exports.EventsUtil || ret;
}
return ret;
}));
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("Chronos.Events", ["Chronos.EventsUtil"], function (EventsUtil) {
return factory(root, root, EventsUtil, true);
});
return;
}
//</amd>
/* istanbul ignore next */
if ("object" === typeof exports) {
// CommonJS
factory(root, exports, require("./util/EventsUtil").EventsUtil);
}
/* istanbul ignore next */
else {
/**
* @depend ./util/EventsUtil.js
*/
// Browser globals
root.Chronos = root.Chronos || {};
factory(root, root.Chronos, root.Chronos.EventsUtil);
}
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, evUtil, hide) {
"use strict";
function Events(defaults) {
var appName = "Events",
attrName = "eventName",
eventId = 0,
lstnrs = {},
fired = [],
prefix = "evId_",
indexer = 0,
cloneData,
eventBufferLimit,
defaultAppName;
defaultAppName = defaults && defaults.appName || "*";
cloneData = (defaults && typeof defaults.cloneEventData === "boolean" ? defaults.cloneEventData : false);
eventBufferLimit = (defaults && !isNaN(defaults.eventBufferLimit) ? defaults.eventBufferLimit : -1);
/**
* This registers to an event only once, if it has fired the bind will be removed
* @param data
* @return {*}
*/
function once(data) {
if (data) {
data.triggerOnce = true;
return bind(data);
} else {
return null;
}
}
/**
* This function allows registering for events with the following structure:
* @param app = {
* eventName: string that is the name of the event that will be triggered like 'click'
* aSync: boolean flag if this call back is called synchronously when the event fires, or after we queue all the listeners
* appName: string that specifies an added identifier for multiple instances of the same event name (click by button1, click by button 2)
* func: function - the callback function which the event data will be passed to
* context: the context which the event data will be run with
* triggerOnce: this is for listening only to the first trigger of this event
* } || app = app name
*
* @param ev = event name
* @param fn = callback function
* @return {*}
*/
function bind(app, ev, fn) {
var evData = app;
if ("string" === typeof app) {
evData = {
appName: app,
eventName: ev,
func: fn
};
}
evData.appName = evData.appName || defaultAppName;
if ("*" !== defaultAppName) {
if ("string" === typeof app && ("function" === typeof ev || "undefined" === typeof ev)) {
evData.eventName = app;
}
}
if (!evData.eventName || !evData.func || ("function" !== typeof evData.func && evData.func.constructor !== Array)) {
evUtil.log("Ev listen has invalid params: evName=[" + evData.eventName + "]", "ERROR", "Events");
return null;
}
if (evData.func.constructor === Array) {
var evIds = [], cloneEvent, cloneId;
for (var i = 0; i < evData.func.length; i++) {
cloneEvent = evUtil.cloneEventData(evData);
cloneEvent.func = evData.func[i];
cloneId = bind(cloneEvent);
evIds.push(cloneId);
}
return evIds;
}
var evId = prefix + (eventId++);
var newObj = {
id: evId,
func: evData.func,
context: evData.context || null,
aSync: evData.aSync ? true : false,
appName: evData.appName,
triggerOnce: evData.triggerOnce || false
};
lstnrs[evData.eventName] = lstnrs[evData.eventName] || [];
lstnrs[evData.eventName].push(newObj);
evUtil.log("Ev listen rgstr: evName=[" + evData.eventName + "] aSync=" + newObj.aSync + " appName=" + newObj.name, "DEBUG", "Events");
evData = null;
app = null;
return evId;
}
/**
* This function allows unbinding according to a permutation of the three parameters
* @param unbindObj
* eventName - the eventName you want to unbind
* func - the pointer to the function you want to unbind
* context - the context you want to unbind
* appName - the specific appName we want to unbind
* OR - eventId
* @return {Boolean}
*/
function unbind(unbindObj) {
if ("*" !== defaultAppName) {
unbindObj.appName = unbindObj.appName || defaultAppName;
}
return evUtil.unbind({
unbindObj: unbindObj,
attrName: attrName,
loggerName: appName,
lstnrs: lstnrs
});
}
/**
* firedEventData can pass two request parameters
* @param app name
* @param evName = event name
* @return {Array}
*/
function hasFired(app, evName) {
if ("undefined" === typeof evName) {
evName = app;
app = defaultAppName;
}
return evUtil.hasFired(fired, app, evName);
}
/**
* This publishes/triggers an event
* @param app = {
* eventName - the name of the event triggered
* appName - optional specifies the identifier it is bound to
* passDataByRef: boolean flag whether this callback will get the reference information of the event or a copy (this allows control of data manipulation)
* data - optional event parameters to be passed to the listeners
* } || app name
* @param evName = event name
* @param data = event data
* @return {*}
*/
function trigger(app, evName, data) {
var triggerData = app;
if ("string" === typeof app) {
triggerData = {
eventName: evName,
appName: app,
data: data
};
}
if ("*" !== defaultAppName) {
triggerData.appName = triggerData.appName || defaultAppName;
if ("string" === typeof app && ("object" === typeof evName || "undefined" === typeof evName)) {
triggerData.eventName = app;
}
}
if (!triggerData || typeof (triggerData.eventName) === "undefined") {
evUtil.log("Ev name not spec for publish", "ERROR", "Events");
triggerData = null;
return null;
}
triggerData.passDataByRef = triggerData.passDataByRef || !cloneData;
_storeEventData(triggerData);
var callBacks = evUtil.getListeners(lstnrs, triggerData.eventName, triggerData.appName);
if (callBacks.length > 0) {
for (var j = 0; j < callBacks.length; j++) {
var eventData = triggerData.passDataByRef ? triggerData.data : evUtil.cloneEventData(triggerData.data);//Clone the event data if there was not an explicit request to passByRef
var eventInformation = {appName: triggerData.appName, eventName: triggerData.eventName};
var callBack = callBacks[j];
if (callBack.aSync || (eventData && eventData.aSync)) {
setTimeout(_createCallBack(callBack, eventData, eventInformation), 0);
} else {
_createCallBack(callBack, eventData, eventInformation)();
}
}
}
triggerData = null;
return (callBacks.length > 0);
}
//------------------- Private methods ------------------------------//
function _createCallBack(callBack, callBackEventData, triggerInformation) {
return function () {
try {
callBack.func.call(callBack.context, callBackEventData, triggerInformation);
callBackEventData = null;//Delete local pointer
if (callBack.triggerOnce) {
unbind(callBack);
}
callBack = null;
} catch (err) {
//noinspection JSUnresolvedVariable
evUtil.log("Error executing " + triggerInformation.eventName + " eventId: " + callBack.id + "e=" + err.message, "ERROR", "Events");
}
};
}
/**
* Stores events so we can later ask for them, can be set to a limited store by defaults on instantiation
* @param triggerData
*/
function _storeEventData(triggerData) {
evUtil.storeEventData({
triggerData: triggerData,
eventBufferLimit: eventBufferLimit,
attrName: attrName,
fired: fired,
index: indexer
});
}
this.once = once;
this.hasFired = hasFired;
this.trigger = trigger;
this.publish = trigger;
this.bind = bind;
this.register = bind;
this.unbind = unbind;
this.unregister = unbind;
}
// attach properties to the exports object to define
// the exported module properties.
if (!hide) {
exports.Events = exports.Events || Events;
}
return Events;
}));
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("Chronos.CommandsUtil", ["Chronos.EventsUtil"], function (EventsUtil) {
return factory(root, root, EventsUtil, true);
});
return;
}
//</amd>
/* istanbul ignore next */
if ("object" === typeof exports) {
// CommonJS
factory(root, exports, require("./EventsUtil").EventsUtil);
}
/* istanbul ignore next */
else {
/**
* @depend ./EventsUtil.js
*/
// Browser globals
root.Chronos = root.Chronos || {};
factory(root, root.Chronos, root.Chronos.EventsUtil);
}
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, evUtil, hide) {
"use strict";
/**
* var cmdObj = {
* cmd: cmd,
* attrName: "cmdName",
* loggerName: "Commands",
* prefix: "_somePrefix",
* id: commandId,
* lstnrs: {}
* };
*/
function bind(cmdObj) {
var cmdName = cmdObj.cmd[cmdObj.attrName];
if (!cmdName || !cmdObj.cmd.func || "function" !== typeof cmdObj.cmd.func || !valid(cmdObj.cmd, cmdName)) {
evUtil.log("comply: has invalid params: command=[" + cmdName + "]", "ERROR", cmdObj.loggerName);
return null;
}
if (cmdObj.lstnrs[cmdName] && cmdObj.lstnrs[cmdName].length) {
evUtil.log("comply: cannot comply because command already exist command=" + cmdName, "ERROR", cmdObj.loggerName);
return null;
}
var cmdId = cmdObj.prefix + (cmdObj.id++);
var newObj = {
id: cmdId,
func: cmdObj.cmd.func,
context: cmdObj.cmd.context || null,
appName: cmdObj.cmd.appName
};
cmdObj.lstnrs[cmdName] = cmdObj.lstnrs[cmdName] || [];
cmdObj.lstnrs[cmdName].push(newObj);
evUtil.log("Cmd comply: evName=[" + cmdName + "] appName=" + newObj.appName, "DEBUG", cmdObj.loggerName);
return cmdId;
}
function valid(cmd, name) {
return !((name && name === "*") || (cmd.appName && cmd.appName === "*"));
}
// attach properties to the exports object to define
// the exported module properties.
var ret = {
bind: bind,
valid: valid
};
if (!hide) {
exports.CommandsUtil = exports.CommandsUtil || ret;
}
return ret;
}));
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("Chronos.Commands", ["Chronos.EventsUtil", "Chronos.CommandsUtil"], function (EventsUtil, CommandsUtil) {
return factory(root, root, EventsUtil, CommandsUtil, true);
});
return;
}
//</amd>
/* istanbul ignore next */
if ("object" === typeof exports) {
// CommonJS
factory(root, exports, require("./util/EventsUtil").EventsUtil, require("./util/CommandsUtil").CommandsUtil);
}
/* istanbul ignore next */
else {
/**
* @depend ./util/EventsUtil.js
* @depend ./util/CommandsUtil.js
*/
// Browser globals
root.Chronos = root.Chronos || {};
factory(root, root.Chronos, root.Chronos.EventsUtil, root.Chronos.CommandsUtil);
}
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, evUtil, cmdUtil, hide) {
"use strict";
function Commands(defaults) {
var appName = "Commands",
attrName = "cmdName",
commandId = 0,
commands = {},
fired = [],
prefix = "cmdId_",
indexer = 0,
cloneData,
eventBufferLimit,
defaultAppName;
defaultAppName = defaults && defaults.appName || "*";
cloneData = (defaults && typeof defaults.cloneEventData === "boolean" ? defaults.cloneEventData : false);
eventBufferLimit = (defaults && !isNaN(defaults.eventBufferLimit) ? defaults.eventBufferLimit : -1);
/**
* This function allows registering for command with the following structure:
* @param cmd = {
* cmdName: string that is the name of the event that will be triggered like 'get'
* appName: string that specifies an added identifier for multiple instances of the same event name (click by button1, click by button 2)
* func: function - the callback function which the event data will be passed to
* context: the context which the event data will be run with
* }
*
* @return {String} - command Id.
*/
function comply(cmd) {
if ("*" !== defaultAppName) {
cmd.appName = cmd.appName || defaultAppName;
}
return cmdUtil.bind({
cmd: cmd,
attrName: attrName,
loggerName: appName,
prefix: prefix,
id: commandId,
lstnrs: commands
});
}
/**
* This function allows unbinding according to a permutation of the three parameters
* @param unbindObj
* cmdName - the eventName you want to unbind
* func - the pointer to the function you want to unbind
* context - the context you want to unbind
* appName - the specific appName we want to unbind
* OR - commandId
* @return {Boolean} - has stopped complying.
*/
function stopComplying(unbindObj) {
if ("*" !== defaultAppName) {
unbindObj.appName = unbindObj.appName || defaultAppName;
}
return evUtil.unbind({
unbindObj: unbindObj,
attrName: attrName,
loggerName: appName,
lstnrs: commands
});
}
/**
* firedEventData can pass two request parameters
* @param app name
* @param cmdName = command name
* @return {Array}
*/
function hasFired(app, cmdName) {
if ("undefined" === typeof cmdName) {
cmdName = app;
app = defaultAppName;
}
return evUtil.hasFired(fired, app, cmdName);
}
/**
* This triggers a command
* @param cmd = {
* cmdName - the name of the command triggered
* appName - optional specifies the identifier it is bound to
* passDataByRef: boolean flag whether this callback will get the reference information of the event or a copy (this allows control of data manipulation)
* data - optional event parameters to be passed to the listeners
* }
*
* @param cb - optional callback to notify when finished
* @return {*}
*/
function command(cmd, cb) {
if (!cmd || typeof (cmd.cmdName) === "undefined" || !cmdUtil.valid(cmd, cmd.cmdName)) {
evUtil.log("CMD name not spec for command", "ERROR", "Commands");
return null;
}
if ("*" !== defaultAppName) {
cmd.appName = cmd.appName || defaultAppName;
}
cmd.passDataByRef = cmd.passDataByRef || !cloneData;
_storeEventData(cmd);
if (!commands[cmd.cmdName]) {
return false;
}
var callBacks = evUtil.getListeners(commands, cmd.cmdName, cmd.appName);
if (callBacks.length > 0) {
for (var j = 0; j < callBacks.length; j++) {
var cmdData = cmd.passDataByRef ? cmd.data : evUtil.cloneEventData(cmd.data);//Clone the event data if there was not an explicit request to passByRef
var callBack = callBacks[j];
try {
if ("function" === typeof cb) {
callBack.func.call(callBack.context, cmdData, cb);
} else {
callBack.func.call(callBack.context, cmdData);
}
cmdData = null;//Delete local pointer
callBack = null;
} catch (err) {
if ("function" === typeof cb) {
try {
cb(err);
} catch (e) {
evUtil.log("Error executing callback on error, " +cmd.cmdName + " commandId: " + callBack.id + "e=" + e.message, "ERROR", "Commands");
}
}
//noinspection JSUnresolvedVariable
evUtil.log("Error executing " + cmd.cmdName + " commandId: " + callBack.id + "e=" + err.message, "ERROR", "Commands");
}
}
}
return (callBacks.length > 0);
}
//------------------- Private methods ------------------------------//
/**
* Stores commands so we can later ask for them, can be set to a limited store by defaults on instantiation
* @param triggerData
*/
function _storeEventData(triggerData) {
evUtil.storeEventData({
triggerData: triggerData,
eventBufferLimit: eventBufferLimit,
attrName: attrName,
fired: fired,
index: indexer
});
}
this.hasFired = hasFired;
this.comply = comply;
this.stopComplying = stopComplying;
this.command = command;
}
// attach properties to the exports object to define
// the exported module properties.
if (!hide) {
exports.Commands = exports.Commands || Commands;
}
return Commands;
}));
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("Chronos.Reqres", ["Chronos.EventsUtil", "Chronos.CommandsUtil"], function (EventsUtil, CommandsUtil) {
return factory(root, root, EventsUtil, CommandsUtil, true);
});
return;
}
//</amd>
/* istanbul ignore next */
if ("object" === typeof exports) {
// CommonJS
factory(root, exports, require("./util/EventsUtil").EventsUtil, require("./util/CommandsUtil").CommandsUtil);
}
/* istanbul ignore next */
else {
/**
* @depend ./util/EventsUtil.js
* @depend ./util/CommandsUtil.js
*/
// Browser globals
root.Chronos = root.Chronos || {};
factory(root, root.Chronos, root.Chronos.EventsUtil, root.Chronos.CommandsUtil);
}
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, evUtil, cmdUtil, hide) {
function ReqRes(defaults) {
var appName = "ReqRes",
attrName = "reqName",
requestId = 0,
requests = {},
fired = [],
prefix = "reqId_",
indexer = 0,
cloneData,
eventBufferLimit,
defaultAppName;
defaultAppName = defaults && defaults.appName || "*";
cloneData = (defaults && typeof defaults.cloneEventData === "boolean" ? defaults.cloneEventData : false);
eventBufferLimit = (defaults && !isNaN(defaults.eventBufferLimit) ? defaults.eventBufferLimit : -1);
/**
* This function allows registering for command with the following structure:
* @param req = {
* reqName: string that is the name of the event that will be triggered like 'get'
* appName: string that specifies an added identifier for multiple instances of the same event name (click by button1, click by button 2)
* func: function - the callback function which the event data will be passed to
* context: the context which the event data will be run with
* }
*
* @return {String} - command Id.
*/
function reply(req) {
if ("*" !== defaultAppName) {
req.appName = req.appName || defaultAppName;
}
return cmdUtil.bind({
cmd: req,
attrName: attrName,
loggerName: appName,
prefix: prefix,
id: requestId,
lstnrs: requests
});
}
/**
* This function allows unbinding according to a permutation of the three parameters
* @param unbindObj
* reqName - the eventName you want to unbind
* func - the pointer to the function you want to unbind
* context - the context you want to unbind
* appName - the specific appName we want to unbind
* OR - requestId
* @return {Boolean} - has stopped complying.
*/
function stopReplying(unbindObj) {
if ("*" !== defaultAppName) {
unbindObj.appName = unbindObj.appName || defaultAppName;
}
return evUtil.unbind({
unbindObj: unbindObj,
attrName: attrName,
loggerName: appName,
lstnrs: requests
});
}
/**
* firedEventData can pass two request parameters
* @param app name
* @param reqName = command name
* @return {Array}
*/
function hasFired(app, reqName) {
if ("undefined" === typeof reqName) {
reqName = app;
app = defaultAppName;
}
return evUtil.hasFired(fired, app, reqName);
}
/**
* This triggers a command
* @param req = {
* reqName - the name of the command triggered
* appName - optional specifies the identifier it is bound to
* passDataByRef: boolean flag whether this callback will get the reference information of the event or a copy (this allows control of data manipulation)
* data - optional event parameters to be passed to the listeners
* }
* @param cb - optional callback to notify when finished
* @return {*}
*/
function request(req, cb) {
var ret;
if (!req || typeof (req.reqName) === "undefined" || !cmdUtil.valid(req, req.reqName)) {
evUtil.log("request: name not spec for command", "ERROR", "ReqRes");
throw new Error("Invalid request object");
}
if ("*" !== defaultAppName) {
req.appName = req.appName || defaultAppName;
}
req.passDataByRef = req.passDataByRef || !cloneData;
_storeEventData(req);
if (!requests[req.reqName]) {
return ret; //return undefined
}
var callBacks = evUtil.getListeners(requests, req.reqName, req.appName);
if (callBacks.length > 0) {
for (var j = 0; j < callBacks.length; j++) {
var reqData = req.passDataByRef ? req.data : evUtil.cloneEventData(req.data);//Clone the event data if there was not an explicit request to passByRef
var requestInformation = {appName: req.appName, reqName: req.reqName};
var callBack = callBacks[j];
try {
if ("function" === typeof cb) {
ret = callBack.func.call(callBack.context, reqData, cb);
} else {
ret = callBack.func.call(callBack.context, reqData);
}
reqData = null;//Delete local pointer
callBack = null;
} catch (err) {
if ("function" === typeof cb) {
try {
cb(err);
} catch (e) {
evUtil.log("Error executing callback on error, " + requestInformation.reqName + " requestId: " + callBack.id + "e=" + e.message, "ERROR", "ReqRes");
}
}
//noinspection JSUnresolvedVariable
evUtil.log("Error executing " + requestInformation.reqName + " requestId: " + callBack.id + "e=" + err.message, "ERROR", "ReqRes");
}
}
}
return ret;
}
//------------------- Private methods ------------------------------//
/**
* Stores requests so we can later ask for them, can be set to a limited store by defaults on instantiation
* @param triggerData
*/
function _storeEventData(triggerData) {
evUtil.storeEventData({
triggerData: triggerData,
eventBufferLimit: eventBufferLimit,
attrName: attrName,
fired: fired,
index: indexer
});
}
this.hasFired = hasFired;
this.request = request;
this.reply = reply;
this.stopReplying = stopReplying;
}
// attach properties to the exports object to define
// the exported module properties.
if (!hide) {
exports.ReqRes = exports.ReqRes || ReqRes;
}
return ReqRes;
}));
// Just a very dumb proxy wrapper to unify
// all events mechanisms inside a single
// channel proxy wrapper
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("Chronos.Channels", ["Chronos.Events", "Chronos.Commands", "Chronos.Reqres"], function (Events, Commands, Reqres) {
return factory(root, root, Events, Commands, Reqres, true);
});
return;
}
//</amd>
/* istanbul ignore next */
if ("object" === typeof exports) {
// CommonJS
factory(root, exports, require("./Events").Events, require("./Commands").Commands, require("./Reqres").ReqRes);
}
/* istanbul ignore next */
else {
/**
* @depend ./Events.js
* @depend ./Commands.js
* @depend ./Reqres.js
*/
// Browser globals
root.Chronos = root.Chronos || {};
factory(root, root.Chronos, root.Chronos.Events, root.Chronos.Commands, root.Chronos.ReqRes);
}
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, Events, Commands, ReqRes, hide) {
function Channels(options) {
options = options || {};
var externalAPIS = [];
var events = options.events || new Events(options.config && options.config.events);
var commands = options.commands || new Commands(options.config && options.config.commands);
var reqres = options.reqres || new ReqRes(options.config && options.config.reqres);
this.once = events.once;
this.hasFiredEvents = events.hasFired;
this.trigger = events.trigger;
this.publish = events.publish;
this.bind = events.bind;
this.register = events.register;
this.unbind = events.unbind;
this.unregister = events.unregister;
this.hasFiredCommands = commands.hasFired;
this.comply = commands.comply;
this.stopComplying = commands.stopComplying;
this.command = commands.command;
this.hasFiredReqres = reqres.hasFired;
this.request = reqres.request;
this.reply = reqres.reply;
this.stopReplying = reqres.stopReplying;
if (options.externalProxy === true) {
this.trigger = _wrapCalls({
func: events.trigger,
context: events,
triggerType: "trigger"
});
this.publish = _wrapCalls({
func: events.publish,
context: events,
triggerType: "trigger"
});
this.registerProxy = registerProxy;
}
/**
* Wraps API calls to trigger other registered functions
* @param options
* @returns {Function}
* @private
*/
function _wrapCalls(options){
return function(){
var api;
options.func.apply(options.context, Array.prototype.slice.call(arguments, 0));
for (var i = 0; i < externalAPIS.length; i++) {
api = externalAPIS[i];
if (api[options.triggerType]) {
try {
api[options.triggerType].apply(api.context,Array.prototype.slice.call(arguments, 0));
}
catch (exc) {}
}
}
};
}
/**
* Registers external proxy for trigger of events
* @param external
*/
function registerProxy(external){
if (typeof external === 'object' && external.trigger) {
externalAPIS.push(external);
}
}
}
// attach properties to the exports object to define
// the exported module properties.
if (!hide) {
exports.Channels = exports.Channels || Channels;
}
return Channels;
}));
;(function (root, factory) {
"use strict";
/* istanbul ignore if */
//<amd>
if ("function" === typeof define && define.amd) {
// AMD. Register as an anonymous module.
define("CircuitBreaker", ["exports"], function () {
if (!root.CircuitBreaker) {
factory(root);
}
return root.CircuitBreaker;
});
return;
}
//</amd>
/* istanbul ignore else */
if ("object" === typeof exports) {
// CommonJS
factory(exports);
}
else {
factory(root);
}
}(typeof CircuitRoot === "undefined" ? this : CircuitRoot , function (root) {
"use strict";
/*jshint validthis:true */
/**
* @type {{OPEN: number, HALF_OPEN: number, CLOSED: number}}
* State representation for the circuit
*/
var STATE = {
OPEN: 0,
HALF_OPEN: 1,
CLOSED: 2
};
/**
* @type {{FAILURE: string, SUCCESS: string, TIMEOUT: string, OUTAGE: string}}
* Measure types for each bucket
*/
var MEASURE = {
FAILURE: "failure",
SUCCESS: "success",
TIMEOUT: "timeout",
OUTAGE: "outage"
};
/**
* CircuitBreaker constructor
* @constructor
* @param {Object} [options] the configuration options for the instance
* @param {Number} [options.slidingTimeWindow = 30000] - the time window that will be used for state calculations [milliseconds]
* @param {Number} [options.bucketsNumber = 10] - the number of the buckets that the time window will be split to (a bucket is a sliding unit that is added/remove from the time window)
* @param {Number} [options.tolerance = 50] - the tolerance before opening the circuit in percentage
* @param {Number} [options.calibration = 5] - the calibration of minimum calls before starting to validate measurements [number]
* @param {Number} [options.timeout = 0] - optional timeout parameter to apply and time the command [number]
* @param {Function} [options.onopen] - handler for open
* @param {Function} [options.onclose] - handler for close
*/
function CircuitBreaker(options) {
// For forcing new keyword
if (false === (this instanceof CircuitBreaker)) {
return new CircuitBreaker(options);
}
this.initialize(options);
}
CircuitBreaker.prototype = (function () {
/**
* Method for initialization
* @param {Object} [options] the configuration options for the instance
* @param {Number} [options.slidingTimeWindow = 30000] - the time window that will be used for state calculations [milliseconds]
* @param {Number} [options.bucketsNumber = 10] - the number of the buckets that the time window will be split to (a bucket is a sliding unit that is added/remove from the time window)
* @param {Number} [options.tolerance = 50] - the tolerance before opening the circuit in percentage
* @param {Number} [options.calibration = 5] - the calibration of minimum calls before starting to validate measurements [number]
* @param {Number} [options.timeout = 0] - optional timeout parameter to apply and time the command [number]
* @param {Function} [options.onopen] - handler for open
* @param {Function} [options.onclose] - handler for close
*/
function initialize(options) {
if (!this.initialized) {
options = options || {};
this.slidingTimeWindow = !isNaN(options.slidingTimeWindow) && 0 < options.slidingTimeWindow ? parseInt(options.slidingTimeWindow, 10) : 30000;
this.bucketsNumber = !isNaN(options.bucketsNumber) && 0 < options.bucketsNumber ? parseInt(options.bucketsNumber, 10) : 10;
this.tolerance = !isNaN(options.tolerance) && 0 < options.tolerance ? parseInt(options.tolerance, 10) : 50;
this.calibration = !isNaN(options.calibration) && 0 < options.calibration ? parseInt(options.calibration, 10) : 5;
this.timeout = !isNaN(options.timeout) && 0 < options.timeout ? parseInt(options.timeout, 10) : 0;
this.onopen = ("function" === typeof options.onopen) ? options.onopen : function() {};
this.onclose = ("function" === typeof options.onclose) ? options.onclose : function() {};
this.buckets = [_createBucket.call(this)];
this.state = STATE.CLOSED;
this.initialized = true;
_startTicking.call(this);
}
}
/**
* Method for assigning a defer execution
* Code waiting for this promise uses this method
* @param {Function} command - the command to run via the circuit
* @param {Function} [fallback] - the fallback to run when circuit is opened
* @param {Function} [timeout] - the timeout for the executed command
*/
function run(command, fallback, timeout) {
if (fallback && "function" !== typeof fallback) {
timeout = fallback;
fallback = void 0;
}
if (isOpen.call(this)) {
_fallback.call(this, fallback || function() {});
return false;
}
else {
return _execute.call(this, command, timeout);
}
}
/**
* Method for forcing the circuit to open
*/
function open() {
this.forced = this.state;
this.state = STATE.OPEN;
}
/**
* Method for forcing the circuit to close
*/
function close() {
this.forced = this.state;
this.state = STATE.CLOSED;
}
/**
* Method for resetting the forcing
*/
function reset() {
this.state = this.forced;
this.forced = void 0;
}
/**
* Method for checking whether the circuit is open
*/
function isOpen() {
return STATE.OPEN === this.state;
}
/**
* Method for calculating the needed metrics based on all calculation buckets
*/
function calculate() {
var bucketErrors;
var percent;
var total = 0;
var error = 0;
for (var i = 0; i < this.buckets.length; i++) {
bucketErrors = (this.buckets[i][MEASURE.FAILURE] + this.buckets[i][MEASURE.TIMEOUT]);
error += bucketErrors;
total += bucketErrors + this.buckets[i][MEASURE.SUCCESS];
}
percent = (error / (total > 0 ? total : 1)) * 100;
return {
total: total,
error: error,
percent: percent
};
}
/**
* Method for the timer tick which manages the buckets
* @private
*/
function _tick() {
if (this.timer) {
clearTimeout(this.timer);
}
_createNextSlidingBucket.call(this);
if (this.bucketIndex > this.bucketsNumber) {
this.bucketIndex = 0;
if (isOpen.call(this)) {