Engine started in coffeescript

This commit is contained in:
Dominic Bosch 2014-04-03 17:41:51 +02:00
parent 1f4ee71cc5
commit 0cb2241ea0
302 changed files with 1679 additions and 7707 deletions

View file

@ -13,16 +13,15 @@ Components Manager
# - [Persistence](persistence.html)
db = require './persistence'
# - [Dynamic Modules](dynamic-modules.html)
dynmod = require './dynamic-modules' #TODO Rename to code-loader
dynmod = require './dynamic-modules'
# - Node.js Modules: [fs](http://nodejs.org/api/fs.html),
# [vm](http://nodejs.org/api/vm.html) and
# [path](http://nodejs.org/api/path.html),
# [path](http://nodejs.org/api/path.html) and
# [events](http://nodejs.org/api/events.html)
fs = require 'fs'
vm = require 'vm'
path = require 'path'
events = require 'events'
eventEmitter = new events.EventEmitter()
###
Module call
@ -33,29 +32,40 @@ Initializes the Components Manager and constructs a new Event Emitter.
###
exports = module.exports = ( args ) =>
@log = args.logger
@ee = new events.EventEmitter()
db args
dynmod args
module.exports
###
Add an event handler (eh) for a certain event (evt).
Current events are:
Add an event handler (eh) that listens for rules.
- init: as soon as an event handler is added, the init events are emitted for all existing rules.
- newRule: If a new rule is activated, the newRule event is emitted
@public addListener ( *evt, eh* )
@param {String} evt
@public addRuleListener ( *eh* )
@param {function} eh
###
exports.addListener = ( evt, eh ) =>
@ee.addListener evt, eh
if evt is 'init'
db.getRules ( err, obj ) =>
@ee.emit 'init', rule for id, rule of obj
exports.addRuleListener = ( eh ) =>
eventEmitter.addListener 'rule', eh
# Fetch all active rules per user
db.getAllActivatedRuleIdsPerUser ( err, objUsers ) ->
# Go through all rules of each user
fGoThroughUsers = ( user, rules ) ->
# Fetch the rules object for each rule in each user
fFetchRule = ( rule ) ->
db.getRule rule, ( err, oRule ) =>
eventEmitter.emit 'rule',
event: 'init'
user: user
rule: JSON.parse oRule
# Go through all rules for each user
fFetchRule rule for rule in rules
# Go through each user
fGoThroughUsers user, rules for user, rules of objUsers
###
Processes a user request coming through the request-handler.
@ -91,7 +101,7 @@ exports.processRequest = ( user, oReq, callback ) ->
hasRequiredParams = ( arrParams, oPayload ) ->
answ =
code: 400
message: "Your request didn't contain all necessary fields! id and params required"
message: "Your request didn't contain all necessary fields! Requires: #{ arrParams.join() }"
return answ for param in arrParams when not oPayload[param]
answ.code = 200
answ.message = 'All required properties found'
@ -125,6 +135,7 @@ getModuleParams = ( user, oPayload, dbMod, callback ) ->
answ.message = oPayload
callback answ
forgeModule = ( user, oPayload, dbMod, callback ) =>
answ = hasRequiredParams [ 'id', 'params', 'lang', 'data' ], oPayload
if answ.code isnt 200
@ -151,6 +162,11 @@ forgeModule = ( user, oPayload, dbMod, callback ) =>
callback answ
commandFunctions =
get_public_key: ( user, oPayload, callback ) ->
callback
code: 200
message: dynmod.getPublicKey()
get_event_pollers: ( user, oPayload, callback ) ->
getModules user, oPayload, db.eventPollers, callback
@ -177,12 +193,12 @@ commandFunctions =
# - event
# - conditions
# - actions
forge_rule: ( user, oPayload, callback ) =>
forge_rule: ( user, oPayload, callback ) ->
answ = hasRequiredParams [ 'id', 'event', 'conditions', 'actions' ], oPayload
if answ.code isnt 200
callback answ
else
db.getRule oPayload.id, ( err, oExisting ) =>
db.getRule oPayload.id, ( err, oExisting ) ->
if oExisting isnt null
answ =
code: 409
@ -198,10 +214,14 @@ commandFunctions =
db.linkRule rule.id, user.username
db.activateRule rule.id, user.username
if oPayload.event_params
db.eventPollers.storeUserParams ep.module, user.username, oPayload.event_params
epModId = rule.event.split( ' -> ' )[0]
db.eventPollers.storeUserParams epModId, user.username, oPayload.event_params
arrParams = oPayload.action_params
db.actionInvokers.storeUserParams id, user.username, JSON.stringify params for id, params of arrParams
@ee.emit 'newRule', strRule
eventEmitter.emit 'rule',
event: 'new'
user: user.username
rule: rule
answ =
code: 200
message: 'Rule stored and activated!'

View file

@ -6,33 +6,17 @@ Dynamic Modules
> with only a few allowed node.js modules.
###
# **Loads Modules:**
# - Node.js Modules: [vm](http://nodejs.org/api/vm.html) and
# [events](http://nodejs.org/api/events.html)
vm = require 'vm'
needle = require 'needle'
# - External Modules: [coffee-script](http://coffeescript.org/)
# - External Modules: [coffee-script](http://coffeescript.org/),
# [cryptico](https://github.com/wwwtyro/cryptico)
cs = require 'coffee-script'
# cryptico = require 'my-cryptico'
# conf = require path.join '..', 'js-coffee', 'config'
# conf opts
# passPhrase = conf.getKeygenPassphrase()
# numBits = 1024
# console.log passPhrase
# oPrivateRSAkey = cryptico.generateRSAKey passPhrase, numBits
# strPublicKey = cryptico.publicKeyString oPrivateRSAkey
# plainText = "Matt, I need you to help me with my Starcraft strategy."
# oEncrypted = cryptico.encrypt plainText, strPublicKey
# console.log oEncrypted.cipher
# oDecrypted = cryptico.decrypt oEncrypted.cipher, oPrivateRSAkey
# console.log oDecrypted.plaintext
cryptico = require 'my-cryptico'
@ -45,9 +29,27 @@ Initializes the dynamic module handler.
###
exports = module.exports = ( args ) =>
@log = args.logger
# FIXME this can't come through the arguments
if not @strPublicKey and args[ 'keygen' ]
passPhrase = args[ 'keygen' ]
numBits = 1024
@oPrivateRSAkey = cryptico.generateRSAKey passPhrase, numBits
@strPublicKey = cryptico.publicKeyString @oPrivateRSAkey
@log.info "Public Key generated: #{ @strPublicKey }"
# plainText = "Matt, I need you to help me with my Starcraft strategy."
# oEncrypted = cryptico.encrypt plainText, strPublicKey
# console.log oEncrypted.cipher
# oDecrypted = cryptico.decrypt oEncrypted.cipher, oPrivateRSAkey
# console.log oDecrypted.plaintext
module.exports
exports.getPublicKey = () =>
@strPublicKey
###
Try to run a JS module from a string, together with the
given parameters. If it is written in CoffeeScript we
@ -59,13 +61,12 @@ compile it first into JS.
@param {Object} params
@param {String} lang
###
exports.compileString = ( src, userId, moduleId, params, lang ) =>
exports.compileString = ( src, userId, modId, params, lang ) =>
answ =
code: 200
message: 'Successfully compiled'
# src = "'use strict;'\n" + src
if lang is '0'
if lang is 'CoffeeScript'
try
src = cs.compile src
catch err
@ -74,7 +75,7 @@ exports.compileString = ( src, userId, moduleId, params, lang ) =>
err.location.first_line
#FIXME not log but debug module is required to provide information to the user
sandbox =
id: userId + '.' + moduleId + '.vm'
id: userId + '.' + modId + '.vm'
params: params
needle: needle
log: console.log
@ -86,6 +87,8 @@ exports.compileString = ( src, userId, moduleId, params, lang ) =>
#kill the child each time? how to determine whether there's still a token in the module?
try
vm.runInNewContext src, sandbox, sandbox.id
# TODO We should investigate memory usage and garbage collection (global.gc())?
# Start Node with the flags nouse_idle_notification and expose_gc, and then when you want to run the GC, just call global.gc().
catch err
answ.code = 400
answ.message = 'Loading Module failed: ' + err.message

193
coffee/engine.coffee Normal file
View file

@ -0,0 +1,193 @@
###
Engine
==================
> The heart of the WebAPI ECA System. The engine loads action invoker modules
> corresponding to active rules actions and invokes them if an appropriate event
> is retrieved.
###
# **Loads Modules:**
# - [Persistence](persistence.html)
db = require './persistence'
# - [Dynamic Modules](dynamic-modules.html)
dynmod = require './dynamic-modules'
listUserRules = {}
isRunning = false
###
Module call
-----------
Initializes the Engine and starts polling the event queue for new events.
@param {Object} args
###
exports = module.exports = ( args ) =>
if not isRunning
isRunning = true
@log = args.logger
db args
dynmod args
pollQueue()
module.exports
###
Add an event handler (eh) that listens for rules.
@public addRuleListener ( *eh* )
@param {function} eh
###
exports.internalEvent = ( evt ) =>
if not listUserRules[evt.user]
listUserRules[evt.user] =
rules: {}
actions: {}
oUser = listUserRules[evt.user]
oRule = evt.rule
if evt.event is 'new' or ( evt.event is 'init' and not oUser.rules[oRule.id] )
oUser.rules[oRule.id] = oRule
updateActionModules oRule
updateActionModules = ( oNewRule ) ->
# Remove all action invoker modules that are not required anymore
fRemoveNotRequired = ( oUser ) ->
# Check whether the action is still existing in a rule
fRequired = ( actionName ) ->
return true for nmRl, oRl of oUser.rules when actionName in oRl.actions
return false
# Go thorugh all actions and check whether the action is still required
delete oUser.actions[action] for action of oUser.actions when not fRequired action
fRemoveNotRequired oUser for name, oUser of listUserRules
# Add action invoker modules that are not yet loaded
fAddRequired = ( userName, oUser ) ->
# Check whether the action is existing in a rule and load if not
fCheckRules = ( oRule ) ->
# Load the action invoker module if it was part of the updated rule or if it's new
fAddIfNewOrNotExisting = ( actionName ) ->
moduleName = (actionName.split ' -> ')[0]
if not oUser.actions[moduleName] or oRule.id is oNewRule.id
db.actionInvokers.getModule moduleName, ( err, obj ) ->
params = {}
res = dynmod.compileString obj.data, userName, moduleName, params, obj.lang
oUser.actions[moduleName] = res.module
fAddIfNewOrNotExisting action for action in oRule.actions
# Go thorugh all actions and check whether the action is still required
fCheckRules oRl for nmRl, oRl of oUser.rules
fAddRequired userName, oUser for userName, oUser of listUserRules
pollQueue = () ->
if isRunning
db.popEvent ( err, obj ) ->
if not err and obj
processEvent obj
setTimeout pollQueue, 50 #TODO adapt to load
###
Checks whether all conditions of the rule are met by the event.
@private validConditions ( *evt, rule* )
@param {Object} evt
@param {Object} rule
###
validConditions = ( evt, rule ) ->
conds = rule.conditions
return false for prop of conds when not evt[prop] or evt[prop] isnt conds[prop]
return true
###
Handles retrieved events.
@private processEvent ( *evt* )
@param {Object} evt
###
processEvent = ( evt ) ->
@log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')');
# var actions = checkEvent(evt);
# console.log('found actions to invoke:');
# console.log(actions);
# for(var user in actions) {
# for(var module in actions[user]) {
# for(var i = 0; i < actions[user][module]['functions'].length; i++) {
# var act = {
# module: module,
# function: actions[user][module]['functions'][i]
# }
# invokeAction(evt, user, act);
# */
# function checkEvent(evt) {
# var actions = {}, tEvt;
# for(var user in listRules) {
# actions[user] = {};
# for(var rule in listRules[user]) {
# //TODO this needs to get depth safe, not only data but eventually also
# // on one level above (eventid and other meta)
# tEvt = listRules[user][rule].event;
# if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) {
# log.info('EN', 'Rule "' + rule + '" fired');
# var oAct = listRules[user][rule].actions;
# console.log (oAct);
# for(var module in oAct) {
# if(!actions[user][module]) {
# actions[user][module] = {
# functions: []
# };
# }
# for(var i = 0; i < oAct[module]['functions'].length; i++ ){
# console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]);
# actions[user][module]['functions'].push(oAct[module]['functions'][i]);
# // if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]);
# }
# }
# }
# }
# }
# return actions;
# }
# /**
# * Invoke an action according to its type.
# * @param {Object} evt The event that invoked the action
# * @param {Object} action The action to be invoked
# */
# function invokeAction( evt, user, action ) {
# console.log('invoking action');
# var actionargs = {};
# //FIXME internal events, such as loopback ha sno arrow
# //TODO this requires change. the module property will be the identifier
# // in the actions object (or shall we allow several times the same action?)
# console.log(action.module);
# console.log(listActionModules);
# var srvc = listActionModules[user][action.module];
# console.log(srvc);
# if(srvc && srvc[action.function]) {
# //FIXME preprocessing not only on data
# //FIXME no preprocessing at all, why don't we just pass the whole event to the action?'
# // preprocessActionArguments(evt.payload, action.arguments, actionargs);
# try {
# if(srvc[action.function]) srvc[action.function](evt.payload);
# } catch(err) {
# log.error('EN', 'during action execution: ' + err);
# }
# }
# else log.info('EN', 'No api interface found for: ' + action.module);
# }
exports.shutDown = () ->
isRunning = false

View file

@ -129,6 +129,9 @@ init = =>
# > Fetch the `http-port` argument
args[ 'http-port' ] = parseInt argv.w || conf.getHttpPort()
args[ 'db-port' ] = parseInt argv.d || conf.getDbPort()
#FIXME this has to come from user input for security reasons:
args[ 'keygen' ] = conf.getKeygenPassphrase()
@log.info 'RS | Initialzing DB'
db args
@ -167,19 +170,9 @@ init = =>
# from engine and event poller
@log.info 'RS | Initialzing module manager'
cm args
cm.addListener 'init', ( evt ) ->
poller.send
event: 'init'
data: evt
cm.addListener 'newRule', ( evt ) ->
poller.send
event: 'newRule'
data: evt
cm.addListener 'init', ( evt ) ->
engine.internalEvent 'init', evt
cm.addListener 'newRule', ( evt ) ->
engine.internalEvent 'newRule', evt
cm.addRuleListener engine.internalEvent
cm.addRuleListener ( evt ) -> poller.send evt
@log.info 'RS | Initialzing http listener'
# The request handler passes certain requests to the module manager
args[ 'request-service' ] = cm.processRequest
@ -195,7 +188,7 @@ Shuts down the server.
shutDown = () =>
@log.warn 'RS | Received shut down command!'
db?.shutDown()
engine?.shutDown()
engine.shutDown()
# We need to call process.exit() since the express server in the http-listener
# can't be stopped gracefully. Why would you stop this system anyways!??
process.exit()

View file

@ -112,7 +112,12 @@ and the engine fetches the same but modularized code from the npm repository via
this also allows us to send privately stored modules and rules encrypted to the user, which will then see it decrypted after it arrived at the browser
\subsection{Request formats}
\subsection{Message Formats}
\subsubsection{Internal Events}
\subsubsubsection{Event Poller}
\subsubsubsection{Rule events}
\subsubsection{Client GUI}
\subsubsection{User commands}
object that has a command as string and an optional payload as a stringified JSON

View file

@ -1,5 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
Components Manager
@ -7,10 +6,12 @@ Components Manager
> The components manager takes care of the dynamic JS modules and the rules.
> Event Poller and Action Invoker modules are loaded as strings and stored in the database,
> then compiled into node modules and rules and used in the engine and event poller.
*/
*/
(function() {
var commandFunctions, db, dynmod, events, exports, forgeModule, fs, getModuleParams, getModules, hasRequiredParams, path, vm;
var commandFunctions, db, dynmod, eventEmitter, events, exports, forgeModule, fs, getModuleParams, getModules, hasRequiredParams, path,
_this = this;
db = require('./persistence');
@ -18,12 +19,11 @@ Components Manager
fs = require('fs');
vm = require('vm');
path = require('path');
events = require('events');
eventEmitter = new events.EventEmitter();
/*
Module call
@ -31,48 +31,55 @@ Components Manager
Initializes the Components Manager and constructs a new Event Emitter.
@param {Object} args
*/
*/
exports = module.exports = (function(_this) {
return function(args) {
_this.log = args.logger;
_this.ee = new events.EventEmitter();
db(args);
dynmod(args);
return module.exports;
};
})(this);
exports = module.exports = function(args) {
_this.log = args.logger;
db(args);
dynmod(args);
return module.exports;
};
/*
Add an event handler (eh) for a certain event (evt).
Current events are:
Add an event handler (eh) that listens for rules.
- init: as soon as an event handler is added, the init events are emitted for all existing rules.
- newRule: If a new rule is activated, the newRule event is emitted
@public addListener ( *evt, eh* )
@param {String} evt
@public addRuleListener ( *eh* )
@param {function} eh
*/
*/
exports.addListener = (function(_this) {
return function(evt, eh) {
_this.ee.addListener(evt, eh);
if (evt === 'init') {
return db.getRules(function(err, obj) {
var id, rule, _results;
_results = [];
for (id in obj) {
rule = obj[id];
_results.push(_this.ee.emit('init', rule));
}
return _results;
});
exports.addRuleListener = function(eh) {
eventEmitter.addListener('rule', eh);
return db.getAllActivatedRuleIdsPerUser(function(err, objUsers) {
var fGoThroughUsers, rules, user, _results;
fGoThroughUsers = function(user, rules) {
var fFetchRule, rule, _i, _len, _results;
fFetchRule = function(rule) {
var _this = this;
return db.getRule(rule, function(err, oRule) {
return eventEmitter.emit('rule', {
event: 'init',
user: user,
rule: JSON.parse(oRule)
});
});
};
_results = [];
for (_i = 0, _len = rules.length; _i < _len; _i++) {
rule = rules[_i];
_results.push(fFetchRule(rule));
}
return _results;
};
_results = [];
for (user in objUsers) {
rules = objUsers[user];
_results.push(fGoThroughUsers(user, rules));
}
};
})(this);
return _results;
});
};
/*
Processes a user request coming through the request-handler.
@ -87,7 +94,8 @@ Components Manager
@param {Object} user
@param {Object} oReq
@param {function} callback
*/
*/
exports.processRequest = function(user, oReq, callback) {
var dat, err;
@ -117,7 +125,7 @@ Components Manager
var answ, param, _i, _len;
answ = {
code: 400,
message: "Your request didn't contain all necessary fields! id and params required"
message: "Your request didn't contain all necessary fields! Requires: " + (arrParams.join())
};
for (_i = 0, _len = arrParams.length; _i < _len; _i++) {
param = arrParams[_i];
@ -132,7 +140,8 @@ Components Manager
getModules = function(user, oPayload, dbMod, callback) {
return dbMod.getAvailableModuleIds(user.username, function(err, arrNames) {
var answReq, fGetFunctions, id, oRes, sem, _i, _len, _results;
var answReq, fGetFunctions, id, oRes, sem, _i, _len, _results,
_this = this;
oRes = {};
answReq = function() {
return callback({
@ -144,18 +153,16 @@ Components Manager
if (sem === 0) {
return answReq();
} else {
fGetFunctions = (function(_this) {
return function(id) {
return dbMod.getModule(id, function(err, oModule) {
if (oModule) {
oRes[id] = JSON.parse(oModule.functions);
}
if (--sem === 0) {
return answReq();
}
});
};
})(this);
fGetFunctions = function(id) {
return dbMod.getModule(id, function(err, oModule) {
if (oModule) {
oRes[id] = JSON.parse(oModule.functions);
}
if (--sem === 0) {
return answReq();
}
});
};
_results = [];
for (_i = 0, _len = arrNames.length; _i < _len; _i++) {
id = arrNames[_i];
@ -179,45 +186,49 @@ Components Manager
}
};
forgeModule = (function(_this) {
return function(user, oPayload, dbMod, callback) {
var answ;
answ = hasRequiredParams(['id', 'params', 'lang', 'data'], oPayload);
if (answ.code !== 200) {
return callback(answ);
} else {
return dbMod.getModule(oPayload.id, function(err, mod) {
var cm, funcs, id, name, src, _ref;
if (mod) {
answ.code = 409;
answ.message = 'Event Poller module name already existing: ' + oPayload.id;
} else {
src = oPayload.data;
cm = dynmod.compileString(src, user.username, oPayload.id, {}, oPayload.lang);
answ = cm.answ;
if (answ.code === 200) {
funcs = [];
_ref = cm.module;
for (name in _ref) {
id = _ref[name];
funcs.push(name);
}
_this.log.info("CM | Storing new module with functions " + (funcs.join()));
answ.message = "Event Poller module successfully stored! Found following function(s): " + funcs;
oPayload.functions = JSON.stringify(funcs);
dbMod.storeModule(user.username, oPayload);
if (oPayload["public"] === 'true') {
dbMod.publish(oPayload.id);
}
forgeModule = function(user, oPayload, dbMod, callback) {
var answ;
answ = hasRequiredParams(['id', 'params', 'lang', 'data'], oPayload);
if (answ.code !== 200) {
return callback(answ);
} else {
return dbMod.getModule(oPayload.id, function(err, mod) {
var cm, funcs, id, name, src, _ref;
if (mod) {
answ.code = 409;
answ.message = 'Event Poller module name already existing: ' + oPayload.id;
} else {
src = oPayload.data;
cm = dynmod.compileString(src, user.username, oPayload.id, {}, oPayload.lang);
answ = cm.answ;
if (answ.code === 200) {
funcs = [];
_ref = cm.module;
for (name in _ref) {
id = _ref[name];
funcs.push(name);
}
_this.log.info("CM | Storing new module with functions " + (funcs.join()));
answ.message = "Event Poller module successfully stored! Found following function(s): " + funcs;
oPayload.functions = JSON.stringify(funcs);
dbMod.storeModule(user.username, oPayload);
if (oPayload["public"] === 'true') {
dbMod.publish(oPayload.id);
}
}
return callback(answ);
});
}
};
})(this);
}
return callback(answ);
});
}
};
commandFunctions = {
get_public_key: function(user, oPayload, callback) {
return callback({
code: 200,
message: dynmod.getPublicKey()
});
},
get_event_pollers: function(user, oPayload, callback) {
return getModules(user, oPayload, db.eventPollers, callback);
},
@ -239,50 +250,53 @@ Components Manager
get_rules: function(user, oPayload, callback) {
return console.log('CM | Implement get_rules');
},
forge_rule: (function(_this) {
return function(user, oPayload, callback) {
var answ;
answ = hasRequiredParams(['id', 'event', 'conditions', 'actions'], oPayload);
if (answ.code !== 200) {
return callback(answ);
} else {
return db.getRule(oPayload.id, function(err, oExisting) {
var arrParams, id, params, rule, strRule;
if (oExisting !== null) {
answ = {
code: 409,
message: 'Rule name already existing!'
};
} else {
rule = {
id: oPayload.id,
event: oPayload.event,
conditions: oPayload.conditions,
actions: oPayload.actions
};
strRule = JSON.stringify(rule);
db.storeRule(rule.id, strRule);
db.linkRule(rule.id, user.username);
db.activateRule(rule.id, user.username);
if (oPayload.event_params) {
db.eventPollers.storeUserParams(ep.module, user.username, oPayload.event_params);
}
arrParams = oPayload.action_params;
for (id in arrParams) {
params = arrParams[id];
db.actionInvokers.storeUserParams(id, user.username, JSON.stringify(params));
}
_this.ee.emit('newRule', strRule);
answ = {
code: 200,
message: 'Rule stored and activated!'
};
forge_rule: function(user, oPayload, callback) {
var answ;
answ = hasRequiredParams(['id', 'event', 'conditions', 'actions'], oPayload);
if (answ.code !== 200) {
return callback(answ);
} else {
return db.getRule(oPayload.id, function(err, oExisting) {
var arrParams, epModId, id, params, rule, strRule;
if (oExisting !== null) {
answ = {
code: 409,
message: 'Rule name already existing!'
};
} else {
rule = {
id: oPayload.id,
event: oPayload.event,
conditions: oPayload.conditions,
actions: oPayload.actions
};
strRule = JSON.stringify(rule);
db.storeRule(rule.id, strRule);
db.linkRule(rule.id, user.username);
db.activateRule(rule.id, user.username);
if (oPayload.event_params) {
epModId = rule.event.split(' -> ')[0];
db.eventPollers.storeUserParams(epModId, user.username, oPayload.event_params);
}
return callback(answ);
});
}
};
})(this)
arrParams = oPayload.action_params;
for (id in arrParams) {
params = arrParams[id];
db.actionInvokers.storeUserParams(id, user.username, JSON.stringify(params));
}
eventEmitter.emit('rule', {
event: 'new',
user: user.username,
rule: rule
});
answ = {
code: 200,
message: 'Rule stored and activated!'
};
}
return callback(answ);
});
}
}
};
}).call(this);

View file

@ -1,20 +1,20 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
Configuration
=============
> Loads the configuration file and acts as an interface to it.
*/
*/
(function() {
var exports, fetchProp, fs, loadConfigFile, path;
var exports, fetchProp, fs, loadConfigFile, path,
_this = this;
fs = require('fs');
path = require('path');
/*
Module call
-----------
@ -24,23 +24,21 @@ Configuration
be generated) and configPath for a custom configuration file path.
@param {Object} args
*/
*/
exports = module.exports = (function(_this) {
return function(args) {
args = args != null ? args : {};
if (args.nolog) {
_this.nolog = true;
}
if (args.configPath) {
loadConfigFile(args.configPath);
} else {
loadConfigFile(path.join('config', 'system.json'));
}
return module.exports;
};
})(this);
exports = module.exports = function(args) {
args = args != null ? args : {};
if (args.nolog) {
_this.nolog = true;
}
if (args.configPath) {
loadConfigFile(args.configPath);
} else {
loadConfigFile(path.join('config', 'system.json'));
}
return module.exports;
};
/*
Tries to load a configuration file from the path relative to this module's parent folder.
@ -48,102 +46,97 @@ Configuration
@private loadConfigFile
@param {String} configPath
*/
*/
loadConfigFile = (function(_this) {
return function(configPath) {
var confProperties, e, prop, _i, _len;
_this.config = null;
confProperties = ['log', 'http-port', 'db-port'];
try {
_this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', configPath)));
_this.isReady = true;
for (_i = 0, _len = confProperties.length; _i < _len; _i++) {
prop = confProperties[_i];
if (!_this.config[prop]) {
_this.isReady = false;
}
}
if (!_this.isReady && !_this.nolog) {
return console.error("Missing property in config file, requires:\n" + (" - " + (confProperties.join("\n - "))));
}
} catch (_error) {
e = _error;
_this.isReady = false;
if (!_this.nolog) {
return console.error("Failed loading config file: " + e.message);
loadConfigFile = function(configPath) {
var confProperties, e, prop, _i, _len;
_this.config = null;
confProperties = ['log', 'http-port', 'db-port'];
try {
_this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', configPath)));
_this.isReady = true;
for (_i = 0, _len = confProperties.length; _i < _len; _i++) {
prop = confProperties[_i];
if (!_this.config[prop]) {
_this.isReady = false;
}
}
};
})(this);
if (!_this.isReady && !_this.nolog) {
return console.error("Missing property in config file, requires:\n" + (" - " + (confProperties.join("\n - "))));
}
} catch (_error) {
e = _error;
_this.isReady = false;
if (!_this.nolog) {
return console.error("Failed loading config file: " + e.message);
}
}
};
/*
Fetch a property from the configuration
@private fetchProp( *prop* )
@param {String} prop
*/
*/
fetchProp = (function(_this) {
return function(prop) {
var _ref;
return (_ref = _this.config) != null ? _ref[prop] : void 0;
};
})(this);
fetchProp = function(prop) {
var _ref;
return (_ref = _this.config) != null ? _ref[prop] : void 0;
};
/*
***Returns*** true if the config file is ready, else false
@public isReady()
*/
*/
exports.isReady = (function(_this) {
return function() {
return _this.isReady;
};
})(this);
exports.isReady = function() {
return _this.isReady;
};
/*
***Returns*** the HTTP port
@public getHttpPort()
*/
*/
exports.getHttpPort = function() {
return fetchProp('http-port');
};
/*
***Returns*** the DB port*
@public getDBPort()
*/
*/
exports.getDbPort = function() {
return fetchProp('db-port');
};
/*
***Returns*** the log conf object
@public getLogConf()
*/
*/
exports.getLogConf = function() {
return fetchProp('log');
};
/*
***Returns*** the crypto key
@public getCryptoKey()
*/
*/
exports.getKeygenPassphrase = function() {
return fetchProp('keygen-passphrase');

View file

@ -1,15 +1,16 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
Dynamic Modules
===============
> Compiles CoffeeScript modules and loads JS modules in a VM, together
> with only a few allowed node.js modules.
*/
*/
(function() {
var cs, exports, needle, vm;
var cryptico, cs, exports, needle, vm,
_this = this;
vm = require('vm');
@ -17,6 +18,7 @@ Dynamic Modules
cs = require('coffee-script');
cryptico = require('my-cryptico');
/*
Module call
@ -24,15 +26,25 @@ Dynamic Modules
Initializes the dynamic module handler.
@param {Object} args
*/
*/
exports = module.exports = (function(_this) {
return function(args) {
_this.log = args.logger;
return module.exports;
};
})(this);
exports = module.exports = function(args) {
var numBits, passPhrase;
_this.log = args.logger;
if (!_this.strPublicKey && args['keygen']) {
passPhrase = args['keygen'];
numBits = 1024;
_this.oPrivateRSAkey = cryptico.generateRSAKey(passPhrase, numBits);
_this.strPublicKey = cryptico.publicKeyString(_this.oPrivateRSAkey);
_this.log.info("Public Key generated: " + _this.strPublicKey);
}
return module.exports;
};
exports.getPublicKey = function() {
return _this.strPublicKey;
};
/*
Try to run a JS module from a string, together with the
@ -44,44 +56,43 @@ Dynamic Modules
@param {String} id
@param {Object} params
@param {String} lang
*/
*/
exports.compileString = (function(_this) {
return function(src, userId, moduleId, params, lang) {
var answ, err, ret, sandbox;
answ = {
code: 200,
message: 'Successfully compiled'
};
if (lang === '0') {
try {
src = cs.compile(src);
} catch (_error) {
err = _error;
answ.code = 400;
answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line;
}
}
sandbox = {
id: userId + '.' + moduleId + '.vm',
params: params,
needle: needle,
log: console.log,
exports: {}
};
exports.compileString = function(src, userId, modId, params, lang) {
var answ, err, ret, sandbox;
answ = {
code: 200,
message: 'Successfully compiled'
};
if (lang === 'CoffeeScript') {
try {
vm.runInNewContext(src, sandbox, sandbox.id);
src = cs.compile(src);
} catch (_error) {
err = _error;
answ.code = 400;
answ.message = 'Loading Module failed: ' + err.message;
answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line;
}
ret = {
answ: answ,
module: sandbox.exports
};
return ret;
}
sandbox = {
id: userId + '.' + modId + '.vm',
params: params,
needle: needle,
log: console.log,
exports: {}
};
})(this);
try {
vm.runInNewContext(src, sandbox, sandbox.id);
} catch (_error) {
err = _error;
answ.code = 400;
answ.message = 'Loading Module failed: ' + err.message;
}
ret = {
answ: answ,
module: sandbox.exports
};
return ret;
};
}).call(this);

View file

@ -1,244 +1,183 @@
'use strict';
// Generated by CoffeeScript 1.6.3
/*
var path = require('path'),
regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
listRules = {},
listActionModules = {},
isRunning = true,
dynmod = require('./dynamic-modules'),
db = require('./persistence'), log;
exports = module.exports = function( args ) {
log = args.logger;
db( args);
dynmod(args);
pollQueue();
return module.exports;
};
var updateActionModules = function() {
for ( var user in listRules ) {
if(!listActionModules[user]) listActionModules[user] = {};
for ( var rule in listRules[user] ) {
var actions = listRules[user][rule].actions;
console.log(actions);
for ( var module in actions ){
for ( var i = 0; i < actions[module]['functions'].length; i++ ){
db.actionInvokers.getModule(module, function( err, objAM ){
db.actionInvokers.getUserParams(module, user, function( err, objParams ) {
console.log (objAM);
//FIXME am name is called 'actions'???
// if(objParams) { //TODO we don't need them for all modules
var answ = dynmod.compileString(objAM.code, objAM.actions + "_" + user, objParams, objAM.lang);
console.log('answ');
console.log(answ);
listActionModules[user][module] = answ.module;
console.log('loaded ' + user + ': ' + module);
console.log(listActionModules);
// }
});
});
}
}
}
}
};
exports.internalEvent = function( evt, data ) {
try {
// TODO do we need to determine init from newRule?
console.log (evt);
console.log (data);
var obj = JSON.parse( data );
db.getRuleActivatedUsers(obj.id, function ( err, arrUsers ) {
console.log (arrUsers);
for(var i = 0; i < arrUsers.length; i++) {
if( !listRules[arrUsers[i]]) listRules[arrUsers[i]] = {};
listRules[arrUsers[i]][obj.id] = obj;
updateActionModules();
}
});
} catch( err ) {
console.log( err );
}
};
Engine
==================
> The heart of the WebAPI ECA System. The engine loads action invoker modules
> corresponding to active rules actions and invokes them if an appropriate event
> is retrieved.
*/
function pollQueue() {
if(isRunning) {
db.popEvent(function (err, obj) {
if(!err && obj) {
processEvent(obj);
}
setTimeout(pollQueue, 50); //TODO adapt to load
});
}
}
(function() {
var db, dynmod, exports, isRunning, listUserRules, pollQueue, processEvent, updateActionModules, validConditions,
_this = this,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
/**
* Handles correctly posted events
* @param {Object} evt The event object
*/
function processEvent(evt) {
log.info('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')');
var actions = checkEvent(evt);
console.log('found actions to invoke:');
console.log(actions);
for(var user in actions) {
for(var module in actions[user]) {
for(var i = 0; i < actions[user][module]['functions'].length; i++) {
var act = {
module: module,
function: actions[user][module]['functions'][i]
}
invokeAction(evt, user, act);
}
}
}
}
db = require('./persistence');
/**
FIXME merge with processEvent
dynmod = require('./dynamic-modules');
* Check an event against the rules repository and return the actions
* if the conditons are met.
* @param {Object} evt the event to check
*/
function checkEvent(evt) {
var actions = {}, tEvt;
for(var user in listRules) {
actions[user] = {};
for(var rule in listRules[user]) {
//TODO this needs to get depth safe, not only data but eventually also
// on one level above (eventid and other meta)
tEvt = listRules[user][rule].event;
if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) {
log.info('EN', 'Rule "' + rule + '" fired');
var oAct = listRules[user][rule].actions;
console.log (oAct);
for(var module in oAct) {
if(!actions[user][module]) {
actions[user][module] = {
functions: []
};
}
for(var i = 0; i < oAct[module]['functions'].length; i++ ){
console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]);
actions[user][module]['functions'].push(oAct[module]['functions'][i]);
// if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]);
}
}
}
}
}
return actions;
}
listUserRules = {};
// {
// "event": "emailyak -> newMail",
// "payload": {
// "TextBody": "hello"
// }
// }
// exports.sendMail = ( args ) ->
// url = 'https://api.emailyak.com/v1/ps1g59ndfcwg10w/json/send/email/'
// data =
// FromAddress: 'tester@mscliveweb.simpleyak.com'
// ToAddress: 'dominic.bosch.db@gmail.com'
// TextBody: 'test'
// needle.post url, JSON.stringify( data ), {json: true}, ( err, resp, body ) ->
// log err
// log body
/**
* Checks whether all conditions of the rule are met by the event.
* @param {Object} evt the event to check
* @param {Object} rule the rule with its conditions
*/
function validConditions(evt, rule) {
for(var property in rule.conditions){
if(!evt[property] || evt[property] != rule.condition[property]) return false;
}
return true;
}
/**
* Invoke an action according to its type.
* @param {Object} evt The event that invoked the action
* @param {Object} action The action to be invoked
*/
function invokeAction( evt, user, action ) {
console.log('invoking action');
var actionargs = {};
//FIXME internal events, such as loopback ha sno arrow
//TODO this requires change. the module property will be the identifier
// in the actions object (or shall we allow several times the same action?)
console.log(action.module);
console.log(listActionModules);
var srvc = listActionModules[user][action.module];
console.log(srvc);
if(srvc && srvc[action.function]) {
//FIXME preprocessing not only on data
//FIXME no preprocessing at all, why don't we just pass the whole event to the action?'
// preprocessActionArguments(evt.payload, action.arguments, actionargs);
try {
if(srvc[action.function]) srvc[action.function](evt.payload);
} catch(err) {
log.error('EN', 'during action execution: ' + err);
}
}
else log.info('EN', 'No api interface found for: ' + action.module);
}
// /**
// * Action properties may contain event properties which need to be resolved beforehand.
// * @param {Object} evt The event whose property values can be used in the rules action
// * @param {Object} act The rules action arguments
// * @param {Object} res The object to be used to enter the new properties
// */
// function preprocessActionArguments(evt, act, res) {
// for(var prop in act) {
// /*
// * If the property is an object itself we go into recursion
// */
// if(typeof act[prop] === 'object') {
// res[prop] = {};
// preprocessActionArguments(evt, act[prop], res[prop]);
// }
// else {
// var txt = act[prop];
// var arr = txt.match(regex);
// * If rules action property holds event properties we resolve them and
// * replace the original action property
// // console.log(evt);
// if(arr) {
// for(var i = 0; i < arr.length; i++) {
// /*
// * The first three characters are '$X.', followed by the property
// */
// var actionProp = arr[i].substring(3).toLowerCase();
// // console.log(actionProp);
// for(var eprop in evt) {
// // our rules language doesn't care about upper or lower case
// if(eprop.toLowerCase() === actionProp) {
// txt = txt.replace(arr[i], evt[eprop]);
// }
// }
// txt = txt.replace(arr[i], '[property not available]');
// }
// }
// res[prop] = txt;
// }
// }
// }
exports.shutDown = function() {
if(log) log.info('EN', 'Shutting down Poller and DB Link');
isRunning = false;
if(db) db.shutDown();
};
/*
Module call
-----------
Initializes the Engine and starts polling the event queue for new events.
@param {Object} args
*/
exports = module.exports = function(args) {
if (!isRunning) {
isRunning = true;
_this.log = args.logger;
db(args);
dynmod(args);
pollQueue();
return module.exports;
}
};
/*
Add an event handler (eh) that listens for rules.
@public addRuleListener ( *eh* )
@param {function} eh
*/
exports.internalEvent = function(evt) {
var oRule, oUser;
if (!listUserRules[evt.user]) {
listUserRules[evt.user] = {
rules: {},
actions: {}
};
}
oUser = listUserRules[evt.user];
oRule = evt.rule;
if (evt.event === 'new' || (evt.event === 'init' && !oUser.rules[oRule.id])) {
oUser.rules[oRule.id] = oRule;
return updateActionModules(oRule);
}
};
updateActionModules = function(oNewRule) {
var fAddRequired, fRemoveNotRequired, name, oUser, userName, _results;
fRemoveNotRequired = function(oUser) {
var action, fRequired, _results;
fRequired = function(actionName) {
var nmRl, oRl, _ref;
_ref = oUser.rules;
for (nmRl in _ref) {
oRl = _ref[nmRl];
if (__indexOf.call(oRl.actions, actionName) >= 0) {
return true;
}
}
return false;
};
_results = [];
for (action in oUser.actions) {
if (!fRequired(action)) {
_results.push(delete oUser.actions[action]);
}
}
return _results;
};
for (name in listUserRules) {
oUser = listUserRules[name];
fRemoveNotRequired(oUser);
}
fAddRequired = function(userName, oUser) {
var fCheckRules, nmRl, oRl, _ref, _results;
fCheckRules = function(oRule) {
var action, fAddIfNewOrNotExisting, _i, _len, _ref, _results;
fAddIfNewOrNotExisting = function(actionName) {
var moduleName;
moduleName = (actionName.split(' -> '))[0];
if (!oUser.actions[moduleName] || oRule.id === oNewRule.id) {
return db.actionInvokers.getModule(moduleName, function(err, obj) {
var params, res;
params = {};
res = dynmod.compileString(obj.data, userName, moduleName, params, obj.lang);
return oUser.actions[moduleName] = res.module;
});
}
};
_ref = oRule.actions;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
_results.push(fAddIfNewOrNotExisting(action));
}
return _results;
};
_ref = oUser.rules;
_results = [];
for (nmRl in _ref) {
oRl = _ref[nmRl];
_results.push(fCheckRules(oRl));
}
return _results;
};
_results = [];
for (userName in listUserRules) {
oUser = listUserRules[userName];
_results.push(fAddRequired(userName, oUser));
}
return _results;
};
pollQueue = function() {
if (isRunning) {
return db.popEvent(function(err, obj) {
if (!err && obj) {
processEvent(obj);
}
return setTimeout(pollQueue, 50);
});
}
};
/*
Checks whether all conditions of the rule are met by the event.
@private validConditions ( *evt, rule* )
@param {Object} evt
@param {Object} rule
*/
validConditions = function(evt, rule) {
var conds, prop;
conds = rule.conditions;
for (prop in conds) {
if (!evt[prop] || evt[prop] !== conds[prop]) {
return false;
}
}
return true;
};
/*
Handles retrieved events.
@private processEvent ( *evt* )
@param {Object} evt
*/
processEvent = function(evt) {
return this.log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')');
};
exports.shutDown = function() {
return isRunning = false;
};
}).call(this);

View file

@ -15,6 +15,7 @@ var logger = require('./logging'),
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
// FIXME Eventually we don't even need to pass these arguments because they are anyways cached even over child_processes
function init() {
if(process.argv.length < 7){
@ -33,7 +34,6 @@ function init() {
var args = { logger: log };
(ml = require('./components-manager'))(args);
(db = require('./persistence'))(args);
initAdminCommands();
initMessageActions();
pollLoop();
log.info('Event Poller instantiated');
@ -106,31 +106,24 @@ function initMessageActions() {
}
};
//TODO this goes into module_manager, this will receive notification about
// new loaded/stored event modules and fetch them from the db
listMessageActions['cmd'] = function(args) {
var func = listAdminCommands[args[1]];
if(typeof(func) === 'function') func(args);
};
process.on('message', function( msg ) {
console.log (JSON.parse(msg.data));
console.log( 'EVENT POLLER GOT MESSAGE!');
console.log( typeof msg);
console.log(msg);
// var arrProps = obj .split('|');
// if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
// else {
// var func = listMessageActions[arrProps[0]];
// if(func) func(arrProps);
// }
console.log('EP internal event handled');
});
// very important so the process doesnt linger on when the paren process is killed
process.on('disconnect', shutDown);
}
function initAdminCommands() {
listAdminCommands['shutdown'] = shutDown
}
function checkRemotes() {
for(var prop in listPoll) {

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
WebAPI-ECA Engine
@ -10,10 +9,12 @@ WebAPI-ECA Engine
> node webapi-eca [opt]
>
> See below in the optimist CLI preparation for allowed optional parameters `[opt]`.
*/
*/
(function() {
var argv, cm, conf, cp, db, engine, fs, http, init, logconf, logger, nameEP, opt, optimist, path, procCmds, shutDown, usage;
var argv, cm, conf, cp, db, engine, fs, http, init, logconf, logger, nameEP, opt, optimist, path, procCmds, shutDown, usage,
_this = this;
logger = require('./logging');
@ -39,10 +40,10 @@ WebAPI-ECA Engine
procCmds = {};
/*
Let's prepare the optimist CLI optional arguments `[opt]`:
*/
*/
usage = 'This runs your webapi-based ECA engine';
@ -129,91 +130,72 @@ WebAPI-ECA Engine
this.log.info('RS | STARTING SERVER');
/*
This function is invoked right after the module is loaded and starts the server.
@private init()
*/
*/
init = (function(_this) {
return function() {
var args;
args = {
logger: _this.log,
logconf: logconf
};
args['http-port'] = parseInt(argv.w || conf.getHttpPort());
args['db-port'] = parseInt(argv.d || conf.getDbPort());
_this.log.info('RS | Initialzing DB');
db(args);
return db.isConnected(function(err) {
var cliArgs, poller;
if (err) {
_this.log.error('RS | No DB connection, shutting down system!');
return shutDown();
} else {
_this.log.info('RS | Initialzing engine');
engine(args);
_this.log.info('RS | Forking a child process for the event poller');
cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog']];
poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs);
_this.log.info('RS | Initialzing module manager');
cm(args);
cm.addListener('init', function(evt) {
return poller.send({
event: 'init',
data: evt
});
});
cm.addListener('newRule', function(evt) {
return poller.send({
event: 'newRule',
data: evt
});
});
cm.addListener('init', function(evt) {
return engine.internalEvent('init', evt);
});
cm.addListener('newRule', function(evt) {
return engine.internalEvent('newRule', evt);
});
_this.log.info('RS | Initialzing http listener');
args['request-service'] = cm.processRequest;
args['shutdown-function'] = shutDown;
return http(args);
}
});
init = function() {
var args;
args = {
logger: _this.log,
logconf: logconf
};
})(this);
args['http-port'] = parseInt(argv.w || conf.getHttpPort());
args['db-port'] = parseInt(argv.d || conf.getDbPort());
args['keygen'] = conf.getKeygenPassphrase();
_this.log.info('RS | Initialzing DB');
db(args);
return db.isConnected(function(err) {
var cliArgs, poller;
if (err) {
_this.log.error('RS | No DB connection, shutting down system!');
return shutDown();
} else {
_this.log.info('RS | Initialzing engine');
engine(args);
_this.log.info('RS | Forking a child process for the event poller');
cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog']];
poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs);
_this.log.info('RS | Initialzing module manager');
cm(args);
cm.addRuleListener(engine.internalEvent);
cm.addRuleListener(function(evt) {
return poller.send(evt);
});
_this.log.info('RS | Initialzing http listener');
args['request-service'] = cm.processRequest;
args['shutdown-function'] = shutDown;
return http(args);
}
});
};
/*
Shuts down the server.
@private shutDown()
*/
*/
shutDown = (function(_this) {
return function() {
_this.log.warn('RS | Received shut down command!');
if (db != null) {
db.shutDown();
}
if (engine != null) {
engine.shutDown();
}
return process.exit();
};
})(this);
shutDown = function() {
_this.log.warn('RS | Received shut down command!');
if (db != null) {
db.shutDown();
}
engine.shutDown();
return process.exit();
};
/*
*# Process Commands
## Process Commands
When the server is run as a child process, this function handles messages
from the parent process (e.g. the testing suite)
*/
*/
process.on('message', function(cmd) {
return typeof procCmds[cmd] === "function" ? procCmds[cmd]() : void 0;

View file

@ -1,156 +1,166 @@
'use strict';
var path = require('path'),
cp = require('child_process'),
log = require('./logging'),
qEvents = new (require('./queue')).Queue(), //TODO export queue into redis
regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
listRules = {},
listActionModules = {},
isRunning = true,
ml, poller, db;
dynmod = require('./dynamic-modules'),
db = require('./persistence'), log;
exports = module.exports = function(args) {
args = args || {};
log(args);
ml = require('./module_loader')(args);
poller = cp.fork(path.resolve(__dirname, 'eventpoller'), [log.getLogType()]);
poller.on('message', function(evt) {
exports.pushEvent(evt);
});
//start to poll the event queue
exports = module.exports = function( args ) {
log = args.logger;
db( args);
dynmod(args);
pollQueue();
return module.exports;
};
/*
* Initialize the rules engine which initializes the module loader.
* @param {Object} db_link the link to the db, see [db\_interface](db_interface.html)
* @param {String} db_port the db port
* @param {String} crypto_key the key to be used for encryption on the db, max legnth 256
*/
exports.addDBLinkAndLoadActionsAndRules = function(db_link) {
//TODO only load rules on beginning, if rules require certain actions, load them in order to allow fast firing
// if rules are set inactive, remove the action module from the memory
db = db_link;
if(ml && db) db.actionInvokers.getModules(function(err, obj) {
if(err) log.error('EN', 'retrieving Action Modules from DB!');
else {
if(!obj) {
log.print('EN', 'No Action Modules found in DB!');
} else {
var m;
for(var el in obj) {
log.print('EN', 'Loading Action Module from DB: ' + el);
try {
m = ml.requireFromString(obj[el], el);
// db.getActionModuleAuth(el, function(mod) {
// return function(err, obj) {
// if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
// };
// }(m));
//FIXME !!!
listActionModules[el] = m;
} catch(e) {
e.addInfo = 'error in action module "' + el + '"';
log.error('EN', e);
}
var updateActionModules = function() {
for ( var user in listRules ) {
if(!listActionModules[user]) listActionModules[user] = {};
for ( var rule in listRules[user] ) {
var actions = listRules[user][rule].actions;
console.log(actions);
for ( var module in actions ){
for ( var i = 0; i < actions[module]['functions'].length; i++ ){
db.actionInvokers.getModule(module, function( err, objAM ){
db.actionInvokers.getUserParams(module, user, function( err, objParams ) {
console.log (objAM);
//FIXME am name is called 'actions'???
// if(objParams) { //TODO we don't need them for all modules
var answ = dynmod.compileString(objAM.code, objAM.actions + "_" + user, objParams, objAM.lang);
console.log('answ');
console.log(answ);
listActionModules[user][module] = answ.module;
console.log('loaded ' + user + ': ' + module);
console.log(listActionModules);
// }
});
});
}
}
}
if(db) db.getRules(function(err, obj) {
for(var el in obj) exports.addRule(JSON.parse(obj[el]));
});
});
else log.severe('EN', new Error('Module Loader or DB not defined!'));
};
/**
* Insert an action module into the list of available interfaces.
* @param {Object} objModule the action module object
*/
//TODO action modules should be loaded once a user activates a rule with the respective
// action, if the user deletes the rule it has to be garrbage collected from the engine's list
exports.loadActionModule = function(name, objModule) {
log.print('EN', 'Action module "' + name + '" loaded');
listActionModules[name] = objModule;
};
/**
* Add a rule into the working memory
* @param {Object} objRule the rule object
*/
exports.addRule = function(objRule) {
//TODO validate rule
log.print('EN', 'Loading Rule');
log.print('EN', 'Loading Rule: ' + objRule.id);
if(listRules[objRule.id]) log.print('EN', 'Replacing rule: ' + objRule.id);
listRules[objRule.id] = objRule;
// Notify poller about eventual candidate
try {
poller.send('event|'+objRule.event);
} catch (err) {
log.print('EN', 'Unable to inform poller about new active rule!');
}
};
exports.internalEvent = function( evt ) {
try {
// TODO do we need to determine init from newRule?
console.log (evt);
// console.log (data);
// var obj = JSON.parse( data );
// db.getRuleActivatedUsers(obj.id, function ( err, arrUsers ) {
// console.log (arrUsers);
// for(var i = 0; i < arrUsers.length; i++) {
// if( !listRules[arrUsers[i]]) listRules[arrUsers[i]] = {};
// listRules[arrUsers[i]][obj.id] = obj;
// updateActionModules();
// }
// });
} catch( err ) {
console.log( err );
}
console.log('internal event handled');
};
function pollQueue() {
if(isRunning) {
var evt = qEvents.dequeue();
if(evt) {
processEvent(evt);
}
setTimeout(pollQueue, 50); //TODO adapt to load
db.popEvent(function (err, obj) {
if(!err && obj) {
processEvent(obj);
}
setTimeout(pollQueue, 50); //TODO adapt to load
});
}
}
/**
* Stores correctly posted events in the queue
* @param {Object} evt The event object
*/
exports.pushEvent = function(evt) {
qEvents.enqueue(evt);
};
/**
* Handles correctly posted events
* @param {Object} evt The event object
*/
function processEvent(evt) {
log.print('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')');
log.info('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')');
var actions = checkEvent(evt);
for(var i = 0; i < actions.length; i++) {
invokeAction(evt, actions[i]);
console.log('found actions to invoke:');
console.log(actions);
for(var user in actions) {
for(var module in actions[user]) {
for(var i = 0; i < actions[user][module]['functions'].length; i++) {
var act = {
module: module,
function: actions[user][module]['functions'][i]
}
invokeAction(evt, user, act);
}
}
}
}
/**
FIXME merge with processEvent
* Check an event against the rules repository and return the actions
* if the conditons are met.
* @param {Object} evt the event to check
*/
function checkEvent(evt) {
var actions = [];
for(var rn in listRules) {
var actions = {}, tEvt;
for(var user in listRules) {
actions[user] = {};
for(var rule in listRules[user]) {
//TODO this needs to get depth safe, not only data but eventually also
// on one level above (eventid and other meta)
if(listRules[rn].event === evt.event && validConditions(evt.payload, listRules[rn])) {
log.print('EN', 'Rule "' + rn + '" fired');
actions = actions.concat(listRules[rn].actions);
tEvt = listRules[user][rule].event;
if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) {
log.info('EN', 'Rule "' + rule + '" fired');
var oAct = listRules[user][rule].actions;
console.log (oAct);
for(var module in oAct) {
if(!actions[user][module]) {
actions[user][module] = {
functions: []
};
}
for(var i = 0; i < oAct[module]['functions'].length; i++ ){
console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]);
actions[user][module]['functions'].push(oAct[module]['functions'][i]);
// if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]);
}
}
}
}
}
return actions;
}
// {
// "event": "emailyak -> newMail",
// "payload": {
// "TextBody": "hello"
// }
// }
// exports.sendMail = ( args ) ->
// url = 'https://api.emailyak.com/v1/ps1g59ndfcwg10w/json/send/email/'
// data =
// FromAddress: 'tester@mscliveweb.simpleyak.com'
// ToAddress: 'dominic.bosch.db@gmail.com'
// TextBody: 'test'
// needle.post url, JSON.stringify( data ), {json: true}, ( err, resp, body ) ->
// log err
// log body
/**
* Checks whether all conditions of the rule are met by the event.
* @param {Object} evt the event to check
* @param {Object} rule the rule with its conditions
*/
function validConditions(evt, rule) {
for(var property in rule.condition){
for(var property in rule.conditions){
if(!evt[property] || evt[property] != rule.condition[property]) return false;
}
return true;
@ -161,76 +171,75 @@ function validConditions(evt, rule) {
* @param {Object} evt The event that invoked the action
* @param {Object} action The action to be invoked
*/
function invokeAction(evt, action) {
var actionargs = {},
arrModule = action.module.split('->');
function invokeAction( evt, user, action ) {
console.log('invoking action');
var actionargs = {};
//FIXME internal events, such as loopback ha sno arrow
//TODO this requires change. the module property will be the identifier
// in the actions object (or shall we allow several times the same action?)
if(arrModule.length < 2) {
log.error('EN', 'Invalid rule detected!');
return;
}
var srvc = listActionModules[arrModule[0]];
if(srvc && srvc[arrModule[1]]) {
console.log(action.module);
console.log(listActionModules);
var srvc = listActionModules[user][action.module];
console.log(srvc);
if(srvc && srvc[action.function]) {
//FIXME preprocessing not only on data
//FIXME no preprocessing at all, why don't we just pass the whole event to the action?'
preprocessActionArguments(evt.payload, action.arguments, actionargs);
// preprocessActionArguments(evt.payload, action.arguments, actionargs);
try {
if(srvc[arrModule[1]]) srvc[arrModule[1]](actionargs);
if(srvc[action.function]) srvc[action.function](evt.payload);
} catch(err) {
log.error('EN', 'during action execution: ' + err);
}
}
else log.print('EN', 'No api interface found for: ' + action.module);
else log.info('EN', 'No api interface found for: ' + action.module);
}
/**
* Action properties may contain event properties which need to be resolved beforehand.
* @param {Object} evt The event whose property values can be used in the rules action
* @param {Object} act The rules action arguments
* @param {Object} res The object to be used to enter the new properties
*/
function preprocessActionArguments(evt, act, res) {
for(var prop in act) {
/*
* If the property is an object itself we go into recursion
*/
if(typeof act[prop] === 'object') {
res[prop] = {};
preprocessActionArguments(evt, act[prop], res[prop]);
}
else {
var txt = act[prop];
var arr = txt.match(regex);
/*
* If rules action property holds event properties we resolve them and
* replace the original action property
*/
// console.log(evt);
if(arr) {
for(var i = 0; i < arr.length; i++) {
/*
* The first three characters are '$X.', followed by the property
*/
var actionProp = arr[i].substring(3).toLowerCase();
// console.log(actionProp);
for(var eprop in evt) {
// our rules language doesn't care about upper or lower case
if(eprop.toLowerCase() === actionProp) {
txt = txt.replace(arr[i], evt[eprop]);
}
}
txt = txt.replace(arr[i], '[property not available]');
}
}
res[prop] = txt;
}
}
}
// /**
// * Action properties may contain event properties which need to be resolved beforehand.
// * @param {Object} evt The event whose property values can be used in the rules action
// * @param {Object} act The rules action arguments
// * @param {Object} res The object to be used to enter the new properties
// */
// function preprocessActionArguments(evt, act, res) {
// for(var prop in act) {
// /*
// * If the property is an object itself we go into recursion
// */
// if(typeof act[prop] === 'object') {
// res[prop] = {};
// preprocessActionArguments(evt, act[prop], res[prop]);
// }
// else {
// var txt = act[prop];
// var arr = txt.match(regex);
// * If rules action property holds event properties we resolve them and
// * replace the original action property
// // console.log(evt);
// if(arr) {
// for(var i = 0; i < arr.length; i++) {
// /*
// * The first three characters are '$X.', followed by the property
// */
// var actionProp = arr[i].substring(3).toLowerCase();
// // console.log(actionProp);
// for(var eprop in evt) {
// // our rules language doesn't care about upper or lower case
// if(eprop.toLowerCase() === actionProp) {
// txt = txt.replace(arr[i], evt[eprop]);
// }
// }
// txt = txt.replace(arr[i], '[property not available]');
// }
// }
// res[prop] = txt;
// }
// }
// }
exports.shutDown = function() {
log.print('EN', 'Shutting down Poller and DB Link');
if(log) log.info('EN', 'Shutting down Poller and DB Link');
isRunning = false;
if(poller) poller.send('cmd|shutdown');
if(db) db.shutDown();
};

View file

@ -16,7 +16,7 @@
"eps": {
"epOne": {
"id":"epOne",
"lang":"0",
"lang":"CoffeeScript",
"data":"\n#\n# EmailYak EVENT POLLER\n#\n# Requires user params:\n# - apikey: The user's EmailYak API key\n#\n\nurl = 'https://api.emailyak.com/v1/' + params.apikey + '/json/get/new/email/'\n\nexports.newMail = ( pushEvent ) ->\n needle.get url, ( err, resp, body ) ->\n if not err and resp.statusCode is 200\n mails = JSON.parse( body ).Emails\n pushEvent mail for mail in mails\n else\n log.error 'Error in EmailYak EM newMail: ' + err.message\n\n",
"public":"false",
"params":"[\"apikey\"]",
@ -24,13 +24,31 @@
},
"epTwo": {
"id":"epTwo",
"lang":"0",
"lang":"CoffeeScript",
"data":"\nurl = 'https://api.emailyak.com/v1/' + params.firstparam + '/json/get/new/email/'\n\nexports.newEvent = ( pushEvent ) ->\n needle.get url, ( err, resp, body ) ->\n if not err and resp.statusCode is 200\n mails = JSON.parse( body ).Emails\n pushEvent mail for mail in mails\n else\n log.error 'Error in EmailYak EM newMail: ' + err.message\n\nexports.randomNess = ( pushEvent ) ->\n console.log 'test runs: ' + params.secondparam\n",
"public":"true",
"params":"[\"firstparam\",\"secondparam\"]",
"functions":"[\"newEvent\",\"randomNess\"]"
}
},
"ais": {
"aiOne": {
"id":"aiOne",
"lang":"CoffeeScript",
"data":"# Send a mail through emailyak\nexports.sendMail = ( args ) ->\n\turl = 'https://api.emailyak.com/v1/' + params.apikey + '/json/send/email/'\n\tpayload =\n\t FromAddress : \"testsender@mscliveweb.simpleyak.com\",\n\t ToAddress: \"dominic.bosch@gmail.com\",\n\t Subject: \"TestMAIL\",\n\t TextBody: \"Hello\"\n\t\n\tneedle.post url, payload, ( err, resp, body ) ->\n\t\tif err\n\t\t\tlog err\n\t\tif resp.statusCode isnt 200\n\t\t\tlog 'Request not successful:'\n\t\t\tlog body\n",
"public":"false",
"params":"[\"apikey\"]",
"functions":"[\"sendMail\"]"
},
"aiTwo": {
"id":"aiTwo",
"lang":"CoffeeScript",
"data":"# Send a mail through emailyak\nexports.sendMail = ( args ) ->\n\turl = 'https://api.emailyak.com/v1/' + params.apikey + '/json/send/email/'\n\tpayload =\n\t FromAddress : \"testsender@mscliveweb.simpleyak.com\",\n\t ToAddress: \"dominic.bosch@gmail.com\",\n\t Subject: \"TestMAIL\",\n\t TextBody: \"Hello\"\n\t\n\tneedle.post url, payload, ( err, resp, body ) ->\n\t\tif err\n\t\t\tlog err\n\t\tif resp.statusCode isnt 200\n\t\t\tlog 'Request not successful:'\n\t\t\tlog body\n",
"public":"false",
"params":"[\"apikey\",\"andmore\"]",
"functions":"[\"sendMail\"]"
}
},
"userparams": {
"epUpOne": {
"apikey": "testkey"
@ -52,6 +70,20 @@
"property": "yourValue2"
},
"actions": []
},
"ruleThree": {
"id": "ruleThree_id",
"event": "custom-test-3",
"conditions": {
"property": "yourValue3"
},
"actions": []
},
"ruleReal": {
"id": "ruleReal",
"event": "epOne -> newMail",
"conditions": {},
"actions": ["aiOne -> sendMail"]
}
},
"users": {

View file

@ -25,12 +25,14 @@ db opts
oUser = objects.users.userOne
oRuleOne = objects.rules.ruleOne
oRuleTwo = objects.rules.ruleTwo
oRuleThree = objects.rules.ruleThree
oEpOne = objects.eps.epOne
oEpTwo = objects.eps.epTwo
exports.tearDown = ( cb ) ->
db.deleteRule oRuleOne.id
db.deleteRule oRuleTwo.id
db.deleteRule oRuleThree.id
setTimeout cb, 100
exports.requestProcessing =
@ -56,33 +58,50 @@ exports.requestProcessing =
test.done()
exports.testListener = ( test ) =>
test.expect 2
test.expect 3
strRuleOne = JSON.stringify oRuleOne
strRuleTwo = JSON.stringify oRuleTwo
strRuleThree = JSON.stringify oRuleThree
db.storeUser oUser
db.storeRule oRuleOne.id, strRuleOne
db.linkRule oRuleOne.id, oUser.username
db.activateRule oRuleOne.id, oUser.username
db.storeRule oRuleTwo.id, strRuleTwo
db.linkRule oRuleTwo.id, oUser.username
db.activateRule oRuleTwo.id, oUser.username
db.storeRule oRuleOne.id, JSON.stringify oRuleOne
request =
command: 'forge_rule'
payload: JSON.stringify oRuleTwo
payload: strRuleThree
cm.addRuleListener ( evt ) =>
strEvt = JSON.stringify evt.rule
console.log evt
if evt.event is 'init'
if strEvt is strRuleOne or strEvt is strRuleTwo
test.ok true, 'Dummy true to fill expected tests!'
if strEvt is strRuleThree
test.ok false, 'Init Rule found test rule number two??'
if evt.event is 'new'
if strEvt is strRuleOne or strEvt is strRuleTwo
test.ok false, 'New Rule got test rule number one??'
if strEvt is strRuleThree
test.ok true, 'Dummy true to fill expected tests!'
cm.addListener 'newRule', ( evt ) =>
try
newRule = JSON.parse evt
catch err
test.ok false, 'Failed to parse the newRule event'
test.deepEqual newRule, oRuleTwo, 'New Rule is not the same!'
test.done()
cm.addListener 'init', ( evt ) =>
try
initRule = JSON.parse evt
catch err
test.ok false, 'Failed to parse the newRule event'
test.deepEqual initRule, oRuleOne, 'Init Rule is not the same!'
fWaitForInit = ->
cm.processRequest oUser, request, ( answ ) =>
if answ.code isnt 200
test.ok false, 'testListener failed: ' + answ.message
test.done()
setTimeout test.done, 500
setTimeout fWaitForInit, 200
@ -96,6 +115,7 @@ exports.moduleHandling =
test.expect 2
db.eventPollers.storeModule oUser.username, oEpOne
db.eventPollers.storeModule oUser.username, oEpTwo
request =
command: 'get_event_pollers'
@ -103,23 +123,25 @@ exports.moduleHandling =
test.strictEqual 200, answ.code, 'GetModules failed...'
oExpected = {}
oExpected[oEpOne.id] = JSON.parse oEpOne.functions
test.strictEqual JSON.stringify(oExpected), answ.message,
oExpected[oEpTwo.id] = JSON.parse oEpTwo.functions
test.deepEqual oExpected, JSON.parse(answ.message),
'GetModules retrieved modules is not what we expected'
test.done()
testGetModuleParams: ( test ) ->
test.expect 2
db.eventPollers.storeModule oUser.username, oEpTwo
db.eventPollers.storeModule oUser.username, oEpOne
request =
command: 'get_event_poller_params'
payload: '{"id": "' + oEpTwo.id + '"}'
payload:
id: oEpOne.id
request.payload = JSON.stringify request.payload
cm.processRequest oUser, request, ( answ ) =>
test.strictEqual 200, answ.code,
'Required Module Parameters did not return 200'
test.strictEqual oEpTwo.params, answ.message,
test.strictEqual oEpOne.params, answ.message,
'Required Module Parameters did not match'
test.done()

View file

@ -0,0 +1,41 @@
fs = require 'fs'
path = require 'path'
try
data = fs.readFileSync path.resolve( 'testing', 'files', 'testObjects.json' ), 'utf8'
try
objects = JSON.parse data
catch err
console.log 'Error parsing standard objects file: ' + err.message
catch err
console.log 'Error fetching standard objects file: ' + err.message
logger = require path.join '..', 'js-coffee', 'logging'
log = logger.getLogger
nolog: true
opts =
logger: log
dm = require path.join '..', 'js-coffee', 'dynamic-modules'
dm opts
exports.testCompile = ( test ) ->
test.expect 5
paramsOne =
testParam: 'First Test'
paramsTwo =
testParam: 'Second Test'
code = "exports.testFunc = () ->\n\tparams.testParam"
result = dm.compileString code, 'userOne', 'moduleOne', paramsOne, 'CoffeeScript'
test.strictEqual 200, result.answ.code
moduleOne = result.module
test.strictEqual paramsOne.testParam, moduleOne.testFunc(), "Other result expected"
result = dm.compileString code, 'userOne', 'moduleOne', paramsTwo, 'CoffeeScript'
test.strictEqual 200, result.answ.code
moduleTwo = result.module
test.strictEqual paramsTwo.testParam, moduleTwo.testFunc(), "Other result expected"
test.notStrictEqual paramsOne.testParam, moduleTwo.testFunc(), "Other result expected"
test.done()

View file

@ -0,0 +1,84 @@
fs = require 'fs'
path = require 'path'
try
data = fs.readFileSync path.resolve( 'testing', 'files', 'testObjects.json' ), 'utf8'
try
objects = JSON.parse data
catch err
console.log 'Error parsing standard objects file: ' + err.message
catch err
console.log 'Error fetching standard objects file: ' + err.message
logger = require path.join '..', 'js-coffee', 'logging'
log = logger.getLogger()
# nolog: true
opts =
logger: log
engine = require path.join '..', 'js-coffee', 'engine'
engine opts
db = require path.join '..', 'js-coffee', 'persistence'
db opts
oUser = objects.users.userOne
oRuleOne = objects.rules.ruleOne
oRuleTwo = objects.rules.ruleTwo
oRuleReal = objects.rules.ruleReal
oEpOne = objects.eps.epOne
oEpTwo = objects.eps.epTwo
oAiOne = objects.ais.aiOne
exports.tearDown = ( cb ) ->
db.deleteRule oRuleOne.id
db.deleteRule oRuleTwo.id
db.deleteRule oRuleReal.id
db.actionInvokers.deleteModule oAiOne.id
db.deleteUser oUser.username
setTimeout cb, 100
exports.ruleEvents =
# init: first registration, multiple registration
# actions loaded and added correctly
# new: new actions added
# old actions removed
# delete: all actions removed if not required anymore
testInit: ( test ) ->
db.storeUser oUser
test.done()
strRuleOne = JSON.stringify oRuleOne
strRuleTwo = JSON.stringify oRuleTwo
strRuleReal = JSON.stringify oRuleReal
db.actionInvokers.storeModule oUser.username, oAiOne
db.storeRule oRuleOne.id, strRuleOne
db.linkRule oRuleOne.id, oUser.username
db.activateRule oRuleOne.id, oUser.username
db.storeRule oRuleTwo.id, strRuleTwo
db.linkRule oRuleTwo.id, oUser.username
db.activateRule oRuleTwo.id, oUser.username
db.storeRule oRuleReal.id, strRuleReal
db.linkRule oRuleReal.id, oUser.username
db.activateRule oRuleReal.id, oUser.username
db.getAllActivatedRuleIdsPerUser ( err, obj ) =>
@existingRules = obj
console.log 'existing'
console.log obj
engine.internalEvent
event: 'init'
user: oUser.username
rule: oRuleReal
fCheckRules = () ->
db.getAllActivatedRuleIdsPerUser ( err, obj ) =>
console.log 'after init'
console.log obj
console.log @existingRules is obj
setTimeout fCheckRules, 500

View file

@ -10,7 +10,7 @@ fOnLoad = () ->
editor.setShowPrintMargin false
$( '#editor_mode' ).change ( el ) ->
if $( this ).val() is '0'
if $( this ).val() is 'CoffeeScript'
editor.getSession().setMode "ace/mode/coffee"
else
editor.getSession().setMode "ace/mode/javascript"
@ -80,24 +80,3 @@ fOnLoad = () ->
window.location.href = 'forge?page=forge_action_invoker'
window.addEventListener 'load', fOnLoad, true
# "ais": {
# "ai1": {
# "code": "
# url = 'https://api.emailyak.com/v1/' + params.apikey + '/json/send/email/'
# exports.sendMail = ( args ) ->
# data:
# FromAddress: "no-reply@mscliveweb.simpleyak.com"
# ToAddress: "test@mscliveweb.simpleyak.com"
# log 'set data, posting now'
# needle.post url, data, ( err, resp, body ) ->
# log 'post returned'
# if not err and resp.statusCode is 200
# log 'Sent mail...'
# else
# log 'Error in EmailYak EM sendMail: ' + err.message
# "
# }
# },

View file

@ -10,7 +10,7 @@ fOnLoad = () ->
editor.setShowPrintMargin false
$( '#editor_mode' ).change ( el ) ->
if $( this ).val() is '0'
if $( this ).val() is 'CoffeeScript'
editor.getSession().setMode "ace/mode/coffee"
else
editor.getSession().setMode "ace/mode/javascript"

View file

@ -1,4 +1,11 @@
strPublicKey = ''
$.post( '/usercommand', command: 'get_public_key' )
.done ( data ) ->
strPublicKey = data.message
.fail ( err ) ->
console.log err
$( '#info' ).text 'Error fetching public key, unable to send user-specific parameters securely'
$( '#info' ).attr 'class', 'error'
fOnLoad = () ->
@ -12,19 +19,19 @@ fOnLoad = () ->
command: 'get_event_poller_params'
payload:
id: arr[0]
obj.payload = JSON.stringify( obj.payload );
$.post( '/usercommand', obj )
.done ( data ) ->
if data.message
arrParams = JSON.parse data.message
$( '#event_poller_params table' ).remove()
if arrParams.length > 0
$( '#event_poller_params' ).text 'Required user-specific params:'
table = $ '<table>'
$( '#event_poller_params' ).append table
fAppendParam = ( name ) ->
tr = $( '<tr>' )
tr.append $( '<td>' ).css 'width', '20px'
tr.append $( '<td>' ).text name
tr.append $( '<td>' ).attr( 'class', 'key' ).text name
inp = $( '<input>' ).attr( 'type', 'password' ).attr 'id', "#{ name }"
tr.append $( '<td>' ).text( ' :' ).append inp
table.append tr
@ -45,13 +52,17 @@ fOnLoad = () ->
command: 'get_event_pollers'
$.post( '/usercommand', obj )
.done ( data ) ->
try
oEps = JSON.parse data.message
catch err
console.error 'ERROR: non-object received from server: ' + data.message
return
fAppendEvents = ( id, events ) ->
try
arrNames = JSON.parse events
$( '#select_event' ).append $( '<option>' ).text id + ' -> ' + name for name in arrNames
catch err
console.error 'ERROR: non-array received from server: ' + events
fAppendEvents id, events for id, events of data.message
fAppendEvent = ( evt ) ->
$( '#select_event' ).append $( '<option>' ).text id + ' -> ' + evt
fAppendEvent evt for evt in events
fAppendEvents id, events for id, events of oEps
fFetchEventParams $( '#select_event option:selected' ).text()
.fail ( err ) ->
console.log err
@ -67,17 +78,20 @@ fOnLoad = () ->
command: 'get_action_invokers'
$.post( '/usercommand', obj )
.done ( data ) ->
try
oAis = JSON.parse data.message
catch err
console.error 'ERROR: non-object received from server: ' + data.message
return
i = 0
fAppendActions = ( id, actions ) ->
try
arrNames = JSON.parse actions
for name in arrNames
$( '#select_actions' ).append $( '<option>' ).attr( 'id', i++ ).text id + ' -> ' + name
arrActionInvoker.push id + ' -> ' + name
catch err
console.error 'ERROR: non-array received from server: ' + actions
fAppendActions id, actions for id, actions of data.message
.fail ( err ) ->
fAppendAction = ( act ) ->
$( '#select_actions' ).append $( '<option>' ).attr( 'id', i++ ).text id + ' -> ' + act
arrActionInvoker.push id + ' -> ' + act
fAppendAction act for act in actions
fAppendActions id, actions for id, actions of oAis
.fail ( err ) ->
console.log err
$( '#info' ).text 'Error fetching event poller'
$( '#info' ).attr 'class', 'error'
@ -86,7 +100,9 @@ fOnLoad = () ->
fFetchActionParams = ( div, name ) ->
obj =
command: 'get_action_invoker_params'
id: name
payload:
id: name
obj.payload = JSON.stringify( obj.payload );
$.post( '/usercommand', obj )
.done ( data ) ->
if data.message
@ -117,7 +133,11 @@ fOnLoad = () ->
tr.append $( '<td>' ).attr( 'class', 'title').text( opt.val() )
table.append tr
if $( '#ap_' + arrAI[0] ).length is 0
div = $( '<div>' ).attr( 'id', 'ap_' + arrAI[0] ).html "<i>#{ arrAI[0] }</i>"
div = $( '<div>' )
.attr( 'id', 'ap_' + arrAI[0] )
div.append $( '<div> ')
.attr( 'class', 'underlined')
.text arrAI[0]
$( '#action_params' ).append div
fFetchActionParams div, arrAI[0]
opt.remove()
@ -149,7 +169,7 @@ fOnLoad = () ->
ep = {}
$( "#event_poller_params tr" ).each () ->
val = $( 'input', this ).val()
name = $( 'td:nth-child(2)', this ).text()
name = $( this ).children( '.key' ).text()
if val is ''
throw new Error "Please enter a value for '#{ name }' in the event module!"
ep[name] = val
@ -159,7 +179,7 @@ fOnLoad = () ->
# Store all selected action invokers
ap = {}
$( '#action_params div' ).each () ->
$( '> div', $( '#action_params' ) ).each () ->
id = $( this ).attr( 'id' ).substring 3
params = {}
$( 'tr', this ).each () ->
@ -168,23 +188,26 @@ fOnLoad = () ->
if val is ''
throw new Error "'#{ key }' missing for '#{ id }'"
params[key] = val
ap[id] = params
encryptedParams = cryptico.encrypt JSON.stringify( params ), strPublicKey
ap[id] = encryptedParams.cipher
acts = []
$( '#selected_actions .title' ).each () ->
acts.push $( this ).text()
encryptedParams = cryptico.encrypt JSON.stringify( ep ), strPublicKey
obj =
command: 'forge_rule'
payload:
id: $( '#input_id' ).val()
event: $( '#select_event option:selected' ).val()
event_params: ep
event_params: encryptedParams.cipher
conditions: {} #TODO Add conditions!
actions: acts
action_params: ap
obj.payload = JSON.stringify obj.payload
$.post( '/usercommand', obj )
.done ( data ) ->
console.log 'success'
$( '#info' ).text data.message
$( '#info' ).attr 'class', 'success'
.fail ( err ) ->

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
(function() {
var fOnLoad;
@ -11,7 +11,7 @@
editor.getSession().setMode("ace/mode/coffee");
editor.setShowPrintMargin(false);
$('#editor_mode').change(function(el) {
if ($(this).val() === '0') {
if ($(this).val() === 'CoffeeScript') {
return editor.getSession().setMode("ace/mode/coffee");
} else {
return editor.getSession().setMode("ace/mode/javascript");

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
(function() {
var fOnLoad;
@ -11,7 +11,7 @@
editor.getSession().setMode("ace/mode/coffee");
editor.setShowPrintMargin(false);
$('#editor_mode').change(function(el) {
if ($(this).val() === '0') {
if ($(this).val() === 'CoffeeScript') {
return editor.getSession().setMode("ace/mode/coffee");
} else {
return editor.getSession().setMode("ace/mode/javascript");

View file

@ -1,6 +1,18 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
(function() {
var fOnLoad;
var fOnLoad, strPublicKey;
strPublicKey = '';
$.post('/usercommand', {
command: 'get_public_key'
}).done(function(data) {
return strPublicKey = data.message;
}).fail(function(err) {
console.log(err);
$('#info').text('Error fetching public key, unable to send user-specific parameters securely');
return $('#info').attr('class', 'error');
});
fOnLoad = function() {
var arrActionInvoker, fFetchActionParams, fFetchEventParams, obj;
@ -15,20 +27,20 @@
id: arr[0]
}
};
obj.payload = JSON.stringify(obj.payload);
return $.post('/usercommand', obj).done(function(data) {
var arrParams, fAppendParam, table, _i, _len, _results;
if (data.message) {
arrParams = JSON.parse(data.message);
$('#event_poller_params table').remove();
if (arrParams.length > 0) {
$('#event_poller_params').text('Required user-specific params:');
table = $('<table>');
$('#event_poller_params').append(table);
fAppendParam = function(name) {
var inp, tr;
tr = $('<tr>');
tr.append($('<td>').css('width', '20px'));
tr.append($('<td>').text(name));
tr.append($('<td>').attr('class', 'key').text(name));
inp = $('<input>').attr('type', 'password').attr('id', "" + name);
tr.append($('<td>').text(' :').append(inp));
return table.append(tr);
@ -51,25 +63,28 @@
command: 'get_event_pollers'
};
$.post('/usercommand', obj).done(function(data) {
var events, fAppendEvents, id, _ref;
var err, events, fAppendEvents, id, oEps;
try {
oEps = JSON.parse(data.message);
} catch (_error) {
err = _error;
console.error('ERROR: non-object received from server: ' + data.message);
return;
}
fAppendEvents = function(id, events) {
var arrNames, err, name, _i, _len, _results;
try {
arrNames = JSON.parse(events);
_results = [];
for (_i = 0, _len = arrNames.length; _i < _len; _i++) {
name = arrNames[_i];
_results.push($('#select_event').append($('<option>').text(id + ' -> ' + name)));
}
return _results;
} catch (_error) {
err = _error;
return console.error('ERROR: non-array received from server: ' + events);
var evt, fAppendEvent, _i, _len, _results;
fAppendEvent = function(evt) {
return $('#select_event').append($('<option>').text(id + ' -> ' + evt));
};
_results = [];
for (_i = 0, _len = events.length; _i < _len; _i++) {
evt = events[_i];
_results.push(fAppendEvent(evt));
}
return _results;
};
_ref = data.message;
for (id in _ref) {
events = _ref[id];
for (id in oEps) {
events = oEps[id];
fAppendEvents(id, events);
}
return fFetchEventParams($('#select_event option:selected').text());
@ -86,26 +101,31 @@
command: 'get_action_invokers'
};
$.post('/usercommand', obj).done(function(data) {
var actions, fAppendActions, i, id, _ref, _results;
var actions, err, fAppendActions, i, id, oAis, _results;
try {
oAis = JSON.parse(data.message);
} catch (_error) {
err = _error;
console.error('ERROR: non-object received from server: ' + data.message);
return;
}
i = 0;
fAppendActions = function(id, actions) {
var arrNames, err, name, _i, _len;
try {
arrNames = JSON.parse(actions);
for (_i = 0, _len = arrNames.length; _i < _len; _i++) {
name = arrNames[_i];
$('#select_actions').append($('<option>').attr('id', i++).text(id + ' -> ' + name));
}
return arrActionInvoker.push(id + ' -> ' + name);
} catch (_error) {
err = _error;
return console.error('ERROR: non-array received from server: ' + actions);
var act, fAppendAction, _i, _len, _results;
fAppendAction = function(act) {
$('#select_actions').append($('<option>').attr('id', i++).text(id + ' -> ' + act));
return arrActionInvoker.push(id + ' -> ' + act);
};
_results = [];
for (_i = 0, _len = actions.length; _i < _len; _i++) {
act = actions[_i];
_results.push(fAppendAction(act));
}
return _results;
};
_ref = data.message;
_results = [];
for (id in _ref) {
actions = _ref[id];
for (id in oAis) {
actions = oAis[id];
_results.push(fAppendActions(id, actions));
}
return _results;
@ -117,8 +137,11 @@
fFetchActionParams = function(div, name) {
obj = {
command: 'get_action_invoker_params',
id: name
payload: {
id: name
}
};
obj.payload = JSON.stringify(obj.payload);
return $.post('/usercommand', obj).done(function(data) {
var arrParams, fAppendActionParam, table, _i, _len, _results;
if (data.message) {
@ -159,7 +182,8 @@
tr.append($('<td>').attr('class', 'title').text(opt.val()));
table.append(tr);
if ($('#ap_' + arrAI[0]).length === 0) {
div = $('<div>').attr('id', 'ap_' + arrAI[0]).html("<i>" + arrAI[0] + "</i>");
div = $('<div>').attr('id', 'ap_' + arrAI[0]);
div.append($('<div> ').attr('class', 'underlined').text(arrAI[0]));
$('#action_params').append(div);
fFetchActionParams(div, arrAI[0]);
}
@ -185,7 +209,7 @@
}
});
return $('#but_submit').click(function() {
var acts, ap, ep, err;
var acts, ap, encryptedParams, ep, err;
try {
if ($('#select_event option:selected').length === 0) {
throw new Error('Please create an Event Poller first!');
@ -197,7 +221,7 @@
$("#event_poller_params tr").each(function() {
var name, val;
val = $('input', this).val();
name = $('td:nth-child(2)', this).text();
name = $(this).children('.key').text();
if (val === '') {
throw new Error("Please enter a value for '" + name + "' in the event module!");
}
@ -207,8 +231,8 @@
throw new Error('Please select at least one action or create one!');
}
ap = {};
$('#action_params div').each(function() {
var id, params;
$('> div', $('#action_params')).each(function() {
var encryptedParams, id, params;
id = $(this).attr('id').substring(3);
params = {};
$('tr', this).each(function() {
@ -220,18 +244,20 @@
}
return params[key] = val;
});
return ap[id] = params;
encryptedParams = cryptico.encrypt(JSON.stringify(params), strPublicKey);
return ap[id] = encryptedParams.cipher;
});
acts = [];
$('#selected_actions .title').each(function() {
return acts.push($(this).text());
});
encryptedParams = cryptico.encrypt(JSON.stringify(ep), strPublicKey);
obj = {
command: 'forge_rule',
payload: {
id: $('#input_id').val(),
event: $('#select_event option:selected').val(),
event_params: ep,
event_params: encryptedParams.cipher,
conditions: {},
actions: acts,
action_params: ap
@ -239,6 +265,7 @@
};
obj.payload = JSON.stringify(obj.payload);
return $.post('/usercommand', obj).done(function(data) {
console.log('success');
$('#info').text(data.message);
return $('#info').attr('class', 'success');
}).fail(function(err) {

View file

@ -1 +1 @@
<script src="ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="js/ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>

View file

@ -1 +1 @@
<script src="ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="js/ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>

View file

@ -1 +1 @@
<script src="ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="js/ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>

View file

@ -1 +1,2 @@
<script src="ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="js/ace-src-min-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="js/cryptico.js" type="text/javascript" charset="utf-8"></script>

View file

@ -1,7 +1,7 @@
Action Invoker Name: <input id="input_id" type="text" />
<select id="editor_mode">
<option value="0">CoffeeScript</option>
<option value="1">JavaScript</option>
<option>CoffeeScript</option>
<option>JavaScript</option>
</select> is public: <input type="checkbox" id="is_public" />
<table id="editor_table">
<tr>

View file

@ -1,7 +1,7 @@
Event Poller Name: <input id="input_id" type="text" />
<select id="editor_mode">
<option value="0">CoffeeScript</option>
<option value="1">JavaScript</option>
<option>CoffeeScript</option>
<option>JavaScript</option>
</select> is public: <input type="checkbox" id="is_public" />
<table id="editor_table">
<tr>

View file

@ -1,6 +1,8 @@
<b>Rule Name: </b><input id="input_id" type="text" /><br><br>
<b>Event: </b><select id="select_event"></select>
<div id="event_poller_params"></div>
<div id="event_poller_params">
<br><div class="underlined">Required user-specific params:</div>
</div>
<br><br>
<b>Actions: </b><select id="select_actions"><option></option></select>
<br><br>

View file

@ -1,130 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Forge A Rule</title>
{{{head_requires}}}
<script type = "text/template" id="templ_rule">
{
"id": "rule_id",
"event": "custom",
"condition": { "property": "yourValue" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Rule#2: $X.subject"
}
}
]
}
</script>
</head>
<body>
{{{div_menubar}}}
<div id="mainbody">
<div id="pagetitle">Hi {{user.username}}, forge your own rules!</div>
<p>
<form id="form_rule">
<div id="div_left">
<input type="hidden" id="command" value="store_rule" />
<textarea id="textarea_rule" rows="20" cols="60"> </textarea>
<p id="required_params">
</p>
<p>
<button id="but_submit">save</button>
</p>
</div>
<div id="div_middle">
<p><b>Available Event Modules:</b></p>
<ul> </ul>
</div>
<div id="div_right">
<p><b>Available Action Modules:</b></p>
<ul> </ul>
</div>
</form>
</p>
</div>
<div id="info"></div>
<script type="text/javascript">
$( "#form_rule" ).submit(function( event ) {
var arrInputs = $( "#form_rule .inp_param" );
console.log(arrInputs);
for( var i = 0; i < arrInputs.length; i++) {
console.log(arrInputs[i]);
}
event.preventDefault();
});
$('#but_submit').click(function() {
// var req_fields =
var obj = {
command : 'store_rule',
data: $('#textarea_rule').val()
};
$.post('/usercommand', obj)
// $.post('/usercommand', $('form#rules_form').serialize())
.done(function(data) {
$('#required_params').children().remove();
if(data.success) {
alert(data.info);
} else {
// TODO fix, same logic twice
console.log(data.actionmodules);
console.log(data.eventmodules);
for(var el in data.eventmodules) {
var oEM = JSON.parse(data.eventmodules[el]);
console.log(oEM);
$('#required_params').append($('<p>').html($('<b>Event Module "'+el+'" requires parameters:</b>')));
for(var eel in oEM){
var inp = '<b>' + eel + ': </b><input type="password" id="em_'+el+'_'+eel+'" class="inp_param" />('+oEM[eel].description+')';
$('#required_params').append($('<p>').html(inp));
}
}
for(var el in data.actionmodules) {
$('#required_params').append($('<p>').html($('<b>Action Module "'+el+'" requires parameters:</b>')));
var oAM = JSON.parse(data.actionmodules[el]);
console.log(oAM);
for(var ael in oAM){
var inp = '<b>' + ael + ': </b><input type="password" id="am_'+el+'_'+ael+'" class="inp_param" />('+oAM[ael].description+')';
$('#required_params').append($('<p>').html(inp));
}
}
}
})
.fail(function(err) {
console.log(err);
alert('Posting of rule failed: ' + err.responseText);
});
});
// TODO fix, same logic twice
$.post('/usercommand', { command: 'get_eventmodules' })
.done(function(data) {
for(var mod in data) {
var mthds = data[mod].methods;
if(mthds) {
var arr = mthds.split(',');
for(var i = 0; i < arr.length; i++) {
$('#div_middle ul').append($('<li>').text(mod + '->' + arr[i]));
}
}
}
});
$.post('/usercommand', { command: 'get_actionmodules' })
.done(function(data) {
for(var mod in data) {
var mthds = data[mod].methods;
if(mthds) {
var arr = mthds.split(',');
for(var i = 0; i < arr.length; i++) {
$('#div_right ul').append($('<li>').text(mod + '->' + arr[i]));
}
}
}
});
$('#textarea_rule').val($('#templ_rule').html());
</script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more