very rude commit, basically porting everything from the original project to create a new clean approach with thorough documentation and testing. This commit relies on the huge TODO list to be solved in order to go into a release state.

This commit is contained in:
Dominic Bosch 2013-11-13 16:21:58 +01:00
parent 09024f287e
commit dfe3df5df5
43 changed files with 2915 additions and 23 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
*~
*.log
*credentials.json
*node_modules
doc
.project
.settings

20
LICENSE
View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 Dominic Bosch
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

25
LICENSE.js Normal file
View file

@ -0,0 +1,25 @@
/*
* LICENSE
* =======
* Copyright (c) 2013 by Dominic Bosch and The
* Regents of the University of Basel. All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF BASEL BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
* BASEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF BASEL SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF BASEL HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Authors: Dominic Bosch <dominic.bosch@stud.unibas.ch>
*
*/

View file

@ -1,4 +1,53 @@
webapi-eca
==========
README: webapi-eca
==================
An ECA engine which acts as a middleware between WebAPI's.
>A Modular ECA Engine Server which acts as a middleware between WebAPI's.
>This folder continues examples of an ECA engine and how certain use cases could be implemented together with a rules language.
>Be sure the user which runs the server doesn't have ANY write rights on the server!
>Malicious modules could capture or destroy your server!
>
>
>The server is started through the [rules_server.js](rules_server.html) module by calling `node rule_server.js`.
Getting started
---------------
Clone project:
git clone https://github.com/dominicbosch/webapi-eca.git
Download and install dependencies:
cd webapi-eca
npm install
Get your [redis](http://redis.io/) instance up and running (and find the port for the config file below) or create your own `db_interface.js`.
Create the configuration file:
mkdir config
vi config/config.json
Insert your settings, for example:
{
"http_port": 8125,
"db_port": 6379,
"crypto_key": "[your key]"
}
Start the server:
node js/server
*Congratulations, your own WebAPI based ECA engine server is now up and running!*
Optional command line tools:
----------------------------
Run test suite:
node run_tests
Create the doc *(to be accessed via the webserver, e.g.: localhost:8125/doc/)*:
node create_doc

24
TODO.js Normal file
View file

@ -0,0 +1,24 @@
/*
* TODO's:
* =======
* - [ ] Redis queue
* - [ ] user handling (personal credentials)
* - [ ] security in terms of users (private key, login)
* - [ ] vm for modules, only give few libraries (no fs!)
* - [ ] rules generator (provide webpage that is used to create rules dependent on the existing modues)
* - [ ] geo location module, test on smartphone
*
*
* TODO's per module:
* ==================
* Testing | clean documentation | Clean error handling (Especially in loading of modules):
*
* - [ ] DB Interface
* - [ ] Engine
* - [ ] Event Poller
* - [ ] HTTP Listener
* - [ ] Logging
* - [ ] Module Loader
* - [ ] Module Manager
* - [ ] Server
*/

1
config/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
config.json

20
create_doc.js Normal file
View file

@ -0,0 +1,20 @@
/*
* # groc Documentation
* Create the documentation to be displayed through the webserver.
*/
require('groc').CLI([
"README.md",
"TODO.js",
"LICENSE.js",
"js/*",
"mod_actions/**/*.js",
"mod_events/**/*.js"
// ,
// "rules/*.json"
],
function(error) {
if (error) {
process.exit(1);
}
}
);

View file

@ -0,0 +1,10 @@
load rules rules
load rules
load action probinder
load actions
load event probinder
load events
-> all these from server started from shell and node (different cwd!)
shutdown
upload rules/actions/events
look at doc ([host]/doc/)

264
js/db_interface.js Normal file
View file

@ -0,0 +1,264 @@
// # DB Interface
// Handles the connection to the database and provides functionalities for
// event/action modules, rules and the encrypted storing of authentication tokens.
// ## General
// General functionality as a wrapper for the module holds initialization,
// encryption/decryption, the retrieval of modules and shut down.
// Modules of the same group, e.g. action modules are registered in an unordered
// set in the database, from where they can be retrieved again. For example a new
// action module has its ID (e.g 'probinder') first registered in the set
// 'action_modules' and then stored in the db with the key 'action\_module\_' + ID
// (e.g. action\_module\_probinder).
'use strict';
var redis = require('redis'),
crypto = require('crypto'),
log = require('./logging'),
crypto_key, db;
// @function init()
/*
* Initializes the DB connection. Requires a port where the DB listens to requests
* and a key that is used for encryptions.
* @param {int} db_port
*/
exports.init = function(db_port, key, cbDone){
if(!db_port || !key) {
log.error('DB', 'No DB port or cipher key defined!');
return null;
}
crypto_key = key;
db = redis.createClient(db_port);
db.on("error", function (err) {
log.error('DB', ' Message from DB: ' + err);
});
if(cbDone) cbDone();
};
/**
* ### encrypt
*/
function encrypt(plainText) {
if(!plainText) return null;
try {
var enciph = crypto.createCipher('aes-256-cbc', crypto_key);
var et = enciph.update(plainText, 'utf8', 'base64') + enciph.final('base64');
log.print('DB', 'Encrypted credentials into: ' + et);
return et;
} catch (err) {
log.error('DB', 'in encrypting: ' + err);
return null;
}
}
/**
* ### decrypt
*/
function decrypt(crypticText) {
if(!crypticText) return null;
try {
var deciph = crypto.createDecipher('aes-256-cbc', crypto_key);
return deciph.update(crypticText, 'base64', 'utf8') + deciph.final('utf8');
} catch (err) {
log.error('DB', 'in decrypting: ' + err);
return null;
}
}
/**
* ### replyHandler
* Abstraction answer handling for simple information replies from the DB.
* @param {String} action the action to be displayed in the output string.
*/
function replyHandler(action) {
return function(err, reply) {
if(err) log.error('DB', ' during "' + action + '": ' + err);
else log.print('DB', action + ': ' + reply);
};
}
/**
* ### getSetRecords
* The general structure for modules is that the key is stored in a set.
* By fetching all set entries we can then fetch all modules, which is
* automated in this function.
*
* @param {String} set the set name how it is stored in the DB
* @param {function} funcSingle the function that fetches single entries from the DB
* @param {function} callback the function to be called on success or error, receives
* arguments (err, obj)
*/
function getSetRecords(set, funcSingle, callback) {
db.smembers(set, function(err, reply) {
if(err) log.error('DB', 'fetching ' + set + ': ' + err);
else {
if(reply.length === 0) {
callback(null, null);
} else {
var semaphore = reply.length, objReplies = {};
setTimeout(function() {
if(semaphore > 0) {
callback('Timeout fetching ' + set, null);
}
}, 1000);
for(var i = 0; i < reply.length; i++){
funcSingle(reply[i], function(prop) {
return function(err, reply) {
if(err) log.error('DB', ' fetching single element: ' + prop);
else {
objReplies[prop] = reply;
if(--semaphore === 0) callback(null, objReplies);
}
};
}(reply[i]));
}
}
}
});
}
// @method shutDown()
// Shuts down the db link.
exports.shutDown = function() { db.quit(); };
// ## Action Modules
/**
* ### storeActionModule
* Store a string representation of an action module in the DB.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeActionModule = function(id, data) {
db.sadd('action_modules', id, replyHandler('storing action module key ' + id));
db.set('action_module_' + id, data, replyHandler('storing action module ' + id));
};
/**
* ### getActionModule(id, callback)
* Query the DB for an action module.
* @param {String} id the module id
* @param {function} callback the callback to receive the answer (err, obj)
*/
exports.getActionModule = function(id, callback) {
if(callback) db.get('action_module_' + id, callback);
};
/**
* ### getActionModules(callback)
* Fetch all action modules.
* @param {function} callback the callback to receive the answer (err, obj)
*/
exports.getActionModules = function(callback) {
getSetRecords('action_modules', exports.getActionModule, callback);
};
/**
* storeActionModuleAuth(id, data)
* Store a string representation of the authentication parameters for an action module.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeActionModuleAuth = function(id, data) {
if(data) {
db.sadd('action_modules_auth', id, replyHandler('storing action module auth key ' + id));
db.set('action_module_' + id +'_auth', encrypt(data), replyHandler('storing action module auth ' + id));
}
};
/**
* ### getActionModuleAuth(id, callback)
* Query the DB for an action module authentication token.
* @param {String} id the module id
* @param {function} callback the callback to receive the answer (err, obj)
*/
exports.getActionModuleAuth = function(id, callback) {
if(callback) db.get('action_module_' + id + '_auth', function(err, txt) { callback(err, decrypt(txt)); });
};
// ## Event Modules
/**
* ### storeEventModule(id, data)
* Store a string representation of an event module in the DB.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeEventModule = function(id, data) {
db.sadd('event_modules', id, replyHandler('storing event module key ' + id));
db.set('event_module_' + id, data, replyHandler('storing event module ' + id));
};
/**
* ### getEventModule(id, callback)
* Query the DB for an event module.
* @param {String} id the module id
* @param {function} callback the callback to receive the answer (err, obj)
*/
exports.getEventModule = function(id, callback) {
if(callback) db.get('event_module_' + id, callback);
};
/**
* ### getEventModules(callback)
* Fetch all event modules.
* @param {function} callback the callback that receives the arguments (err, obj)
*/
exports.getEventModules = function(callback) {
getSetRecords('event_modules', exports.getEventModule, callback);
};
/**
* ### storeEventModuleAuth(id, data)
* Store a string representation of he authentication parameters for an event module.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeEventModuleAuth = function(id, data) {
if(data) {
db.sadd('event_modules_auth', id, replyHandler('storing event module auth key ' + id));
db.set('event_module_' + id +'_auth', encrypt(data), replyHandler('storing event module auth ' + id));
}
};
// @method getEventModuleAuth(id, callback)
// Query the DB for an event module authentication token.
// @param {String} id the module id
// @param {function} callback the callback to receive the answer (err, obj)
exports.getEventModuleAuth = function(id, callback) {
if(callback) db.get('event_module_' + id +'_auth', function(err, txt) { callback(err, decrypt(txt)); });
};
// ## Rules
// @method storeRule(id, data)
// Store a string representation of a rule in the DB.
// @param {String} id the unique identifier of the rule
// @param {String} data the string representation
exports.storeRule = function(id, data) {
db.sadd('rules', id, replyHandler('storing rule key ' + id));
db.set('rule_' + id, data, replyHandler('storing rule ' + id));
};
// @method getRule(id, callback)
// Query the DB for a rule.
// @param {String} id the rule id
// @param {function} callback the callback to receive the answer (err, obj)
exports.getRule = function(id, callback) {
db.get('rule_' + id, callback);
};
// @method getRules(callback)
// Fetch all rules from the database.
// @param {function} callback the callback to receive the answer (err, obj)
exports.getRules = function(callback) {
getSetRecords('rules', exports.getRule, callback);
};

253
js/engine.js Normal file
View file

@ -0,0 +1,253 @@
'use strict';
var path = require('path'),
cp = require('child_process'),
ml = require('./module_loader'),
log = require('./logging'),
poller, db, isRunning = true,
qEvents = new (require('./queue')).Queue(); // export queue into redis
var regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
listRules = {},
listActionModules = {},
actionsLoaded = false, eventsLoaded = false;
/*
* 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
*/
function init(db_link, db_port, crypto_key) {
db = db_link;
loadActions();
poller = cp.fork(path.resolve(__dirname, 'eventpoller'), [db_port, crypto_key]);
poller.on('message', function(evt) {
if(evt.event === 'ep_finished_loading') {
eventsLoaded = true;
tryToLoadRules();
} else pushEvent(evt);
});
//start to poll the event queue
pollQueue();
}
function loadActions() {
db.getActionModules(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!');
actionsLoaded = true;
tryToLoadRules();
} else {
var m, semaphore = 0;
for(var el in obj) {
semaphore++;
log.print('EN', 'Loading Action Module from DB: ' + el);
m = ml.requireFromString(obj[el], el);
db.getActionModuleAuth(el, function(mod) {
return function(err, obj) {
if(--semaphore == 0) {
actionsLoaded = true;
tryToLoadRules();
}
if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
};
}(m));
listActionModules[el] = m;
}
}
}
});
}
function tryToLoadRules() {
if(eventsLoaded && actionsLoaded) {
db.getRules(function(err, obj) {
for(var el in obj) loadRule(JSON.parse(obj[el]));
});
}
}
/**
* Insert an action module into the list of available interfaces.
* @param {Object} objModule the action module object
*/
function loadActionModule(name, objModule) {
log.print('EN', 'Action module "' + name + '" loaded');
listActionModules[name] = objModule;
}
/**
* Insert a rule into the eca rules repository
* @param {Object} objRule the rule object
*/
function loadRule(objRule) {
//TODO validate 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!');
}
}
function pollQueue() {
if(isRunning) {
var evt = qEvents.dequeue();
if(evt) {
processEvent(evt);
}
setTimeout(pollQueue, 50); //TODO adapt to load
}
}
/**
* Stores correctly posted events in the queue
* @param {Object} evt The event object
*/
function pushEvent(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 + ')');
var actions = checkEvent(evt);
for(var i = 0; i < actions.length; i++) {
invokeAction(evt, actions[i]);
}
}
/**
* 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) {
//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.data, listRules[rn])) {
log.print('EN', 'Rule "' + rn + '" fired');
actions = actions.concat(listRules[rn].actions);
}
}
return actions;
}
/**
* 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){
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, action) {
var actionargs = {},
arrModule = action.module.split('->');
if(arrModule.length < 2) {
log.error('EN', 'Invalid rule detected!');
return;
}
var srvc = listActionModules[arrModule[0]];
if(srvc && srvc[arrModule[1]]) {
//FIXME preprocessing not only on data
preprocessActionArguments(evt.data, action.arguments, actionargs);
try {
if(srvc[arrModule[1]]) srvc[arrModule[1]](actionargs);
} catch(err) {
log.error('EN', 'during action execution: ' + err);
}
}
else log.print('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;
}
}
}
function loadEventModule(args, answHandler) {
if(args && args.name) {
answHandler.answerSuccess('Loading event module ' + args.name + '...');
poller.send('cmd|loadevent|'+args.name);
} else if(args) answHandler.answerError(args.name + ' not found');
}
function loadEventModules(args, answHandler) {
answHandler.answerSuccess('Loading event moules...');
poller.send('cmd|loadevents');
}
function shutDown() {
log.print('EN', 'Shutting down Poller and DB Link');
isRunning = false;
poller.send('cmd|shutdown');
db.shutDown();
}
exports.init = init;
exports.loadActionModule = loadActionModule;
exports.loadRule = loadRule;
exports.loadEventModule = loadEventModule;
exports.loadEventModules = loadEventModules;
exports.pushEvent = pushEvent;
exports.shutDown = shutDown;

146
js/eventpoller.js Normal file
View file

@ -0,0 +1,146 @@
// # Event Poller
'use strict';
if(process.argv.length < 3) {
log.error('EP', 'No DB port defined! Not starting poller...');
} else {
(function() {
var fs = require('fs'),
path = require('path'),
log = require('./logging'),
db = require('./db_interface'),
ml = require('./module_loader'),
listMessageActions = {},
listAdminCommands = {},
listEventModules = {},
listPoll = {}, //TODO this will change in the future because it could have
//several parameterized (user-specific) instances of each event module
isRunning = true,
eId = 0;
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
db.init(process.argv[2], process.argv[3]);
//TODO eventpoller will not load event modules from filesystem, this will be done by
// the moduel manager and the eventpoller receives messages about new/updated active rules
db.getEventModules(function(err, obj) {
if(err) log.error('EP', 'retrieving Event Modules from DB!');
else {
if(!obj) {
log.print('EP', 'No Event Modules found in DB!');
process.send({ event: 'ep_finished_loading' });
} else {
var m, semaphore = 0;
for(var el in obj) {
semaphore++;
m = ml.requireFromString(obj[el], el);
db.getEventModuleAuth(el, function(mod) {
return function(err, obj) {
if(--semaphore === 0) process.send({ event: 'ep_finished_loading' });
if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
};
}(m));
log.print('EP', 'Loading Event Module: ' + el);
listEventModules[el] = m;
}
}
}
});
listMessageActions['event'] = function(args) {
var prop = args[1], arrModule = prop.split('->');
// var arrModule = obj.module.split('->');
if(arrModule.length > 1){
var module = listEventModules[arrModule[0]];
for(var i = 1; i < arrModule.length; i++) {
if(module) module = module[arrModule[i]];
}
if(module) {
log.print('EP', 'Found active event module "' + prop + '", adding it to polling list');
//FIXME change this to [module][prop] = module; because like this identical properties get overwritten
// also add some on a per user basis information because this should go into a user context for the users
// that sat up this rule!
listPoll[prop] = module;
} else {
log.print('EP', 'No property "' + prop + '" found');
}
}
};
listAdminCommands['loadevent'] = function(args) {
ml.loadModule('mod_events', args[2], loadEventCallback);
};
listAdminCommands['loadevents'] = function(args) {
ml.loadModules('mod_events', loadEventCallback);
};
listAdminCommands['shutdown'] = function(args) {
log.print('EP', 'Shutting down DB Link');
isRunning = false;
db.shutDown();
};
//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(strProps) {
var arrProps = strProps.split('|');
if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
else {
var func = listMessageActions[arrProps[0]];
if(func) func(arrProps);
}
});
function loadEventCallback(name, data, mod, auth) {
db.storeEventModule(name, data); // store module in db
if(auth) db.storeEventModuleAuth(name, auth);
listEventModules[name] = mod; // store compiled module for polling
}
function checkRemotes() {
for(var prop in listPoll) {
try {
listPoll[prop](
/*
* define and immediately call anonymous function with param prop.
* This places the value of prop into the context of the callback
* and thus doesn't change when the for loop keeps iterating over listPoll
*/
(function(p) {
return function(err, obj) {
if(err) {
err.additionalInfo = 'module: ' + p;
log.error('EP', err);
} else {
process.send({
event: p,
eventid: 'polled_' + eId++,
data: obj
});
}
};
})(prop)
);
} catch (e) {
log.error('EP', e);
}
}
}
function pollLoop() {
if(isRunning) {
checkRemotes();
setTimeout(pollLoop, 10000);
}
}
pollLoop();
})();
}

96
js/http_listener.js Normal file
View file

@ -0,0 +1,96 @@
// # HTTP Listener
// Isso
'use strict';
var path = require('path'),
express = require('express'),
port = express(),
log = require('./logging'),
qs = require('querystring'),
adminHandler, eventHandler, server;
function init(http_port, funcAdminHandler, funcEvtHandler) {
if(!http_port || !funcEvtHandler) {
log.error('HL', 'ERROR: either port or eventHandler function not defined!');
return;
}
adminHandler = funcAdminHandler;
eventHandler = funcEvtHandler;
port.use('/doc/', express.static(path.resolve(__dirname, '..', 'doc/')));
port.get('/admin', onAdminCommand);
port.post('/pushEvents', onPushEvent);
server = port.listen(http_port); // inbound event channel
log.print('HL', 'Started listening for http requests on port ' + http_port);
}
function answerHandler(r) {
var response = r, hasBeenAnswered = false;
function postAnswer(msg) {
if(!hasBeenAnswered) {
response.write(msg);
response.end();
hasBeenAnswered = true;
}
}
return {
answerSuccess: function(msg) {
if(!hasBeenAnswered) response.writeHead(200, { "Content-Type": "text/plain" });
postAnswer(msg);
},
answerError: function(msg) {
if(!hasBeenAnswered) response.writeHead(400, { "Content-Type": "text/plain" });
postAnswer(msg);
},
isAnswered: function() { return hasBeenAnswered; }
};
};
/**
* Handles correct event posts, replies thank you.
*/
function answerSuccess(resp, msg){
resp.writeHead(200, { "Content-Type": "text/plain" });
resp.write(msg);
resp.end();
}
/**
* Handles erroneous requests.
* @param {Object} msg the error message to be returned
*/
function answerError(resp, msg) {
resp.writeHead(400, { "Content-Type": "text/plain" });
resp.write(msg);
resp.end();
}
//FIXME this answer handling is a very ugly hack, improve!
function onAdminCommand(request, response) {
var q = request.query;
log.print('HL', 'Received admin request: ' + request.originalUrl);
if(q.cmd) {
adminHandler(q, answerHandler(response));
// answerSuccess(response, 'Thank you, we try our best!');
} else answerError(response, 'I\'m not sure about what you want from me...');
}
/**
* If a request is made to the server, this function is used to handle it.
*/
function onPushEvent(request, response) {
var body = '';
request.on('data', function (data) { body += data; });
request.on('end', function () {
var obj = qs.parse(body);
/* If required event properties are present we process the event */
if(obj && obj.event && obj.eventid){
answerSuccess(response, 'Thank you for the event (' + obj.event + '[' + obj.eventid + '])!');
eventHandler(obj);
} else answerError(response, 'Your event was missing important parameters!');
});
}
exports.init = init;
exports.shutDown = function() {
log.print('HL', 'Shutting down HTTP listener');
process.exit(); // This is a bit brute force...
};

37
js/logging.js Normal file
View file

@ -0,0 +1,37 @@
/*
* Logging
* =======
* Functions to handle logging and errors.
*/
// @function print(module, msg)
/*
* Prints a log to stdout.
* @param {String} module
* @param {String} msg
*/
exports.print = function(module, msg) {
console.log((new Date()).toISOString() + ' | ' + module + ' | ' + msg);
};
// @function error(module, msg)
/*
* Prints a log to stderr.
* @param {String} module
* @param {Error} err
*/
exports.error = function(module, err) {
var ts = (new Date()).toISOString() + ' | ', ai = '';
if(typeof err === 'string') {
var e = new Error();
if(module) console.error(ts + module + ' | ERROR AND BAD HANDLING: ' + err + '\n' + e.stack);
else console.error(ts + '!N/A! | ERROR, BAD HANDLING AND NO MODULE NAME: ' + err + '\n' + e.stack);
} else {
if(err.addInfo) ai = ' (' + err.addInfo + ')';
if(!err.message) err.message = 'UNKNOWN REASON!\n' + err.stack;
if(module) console.error(ts + module + ' | ERROR'+ai+': ' + err.message);
else console.error(ts + '!N/A! | ERROR AND NO MODULE NAME'+ai+': ' + err.message + '\n' + err.stack);
}
};

69
js/module_loader.js Normal file
View file

@ -0,0 +1,69 @@
var fs = require('fs'),
path = require('path'),
log = require('./logging');
function requireFromString(src, name, dir) {
if(!dir) dir = __dirname;
//FIXME load modules only into a safe environment with given modules, no access to whole application
var id = path.resolve(dir, name, name + '.js');
var m = new module.constructor(id, module);
m.paths = module.paths;
try {
m._compile(src);
} catch(err) {
err.addInfo = 'during compilation of module ' + name;
log.error('LM', err);
// log.error('LM', ' during compilation of ' + name + ': ' + err);
}
return m.exports;
}
function loadModule(directory, name, callback) {
try {
fs.readFile(path.resolve(__dirname, '..', directory, name, name + '.js'), 'utf8', function (err, data) {
if (err) {
log.error('LM', 'Loading module file!');
return;
}
var mod = requireFromString(data, name, directory);
if(mod && fs.existsSync(path.resolve(__dirname, '..', directory, name, 'credentials.json'))) {
fs.readFile(path.resolve(__dirname, '..', directory, name, 'credentials.json'), 'utf8', function (err, auth) {
if (err) {
log.error('LM', 'Loading credentials file for "' + name + '"!');
callback(name, data, mod, null);
return;
}
if(mod.loadCredentials) mod.loadCredentials(JSON.parse(auth));
callback(name, data, mod, auth);
});
} else {
// Hand back the name, the string contents and the compiled module
callback(name, data, mod, null);
}
});
} catch(err) {
log.error('LM', 'Failed loading module "' + name + '"');
}
}
function loadModules(directory, callback) {
fs.readdir(path.resolve(__dirname, '..', directory), function (err, list) {
if (err) {
log.error('LM', 'loading modules directory: ' + err);
return;
}
log.print('LM', 'Loading ' + list.length + ' modules from "' + directory + '"');
list.forEach(function (file) {
fs.stat(path.resolve(__dirname, '..', directory, file), function (err, stat) {
if (stat && stat.isDirectory()) {
loadModule(directory, file, callback);
}
});
});
});
}
exports.loadModule = loadModule;
exports.loadModules = loadModules;
exports.requireFromString = requireFromString;

100
js/module_manager.js Normal file
View file

@ -0,0 +1,100 @@
/*
# Module Manager
> The module manager takes care of the module and rules loading in the initialization
> phase and on user request.
> Event and Action modules are loaded as strings and stored in the database,
> then compiled into node modules and and rules
*/
var fs = require('fs'),
path = require('path'),
log = require('./logging'),
ml = require('./module_loader'),
db = null, funcLoadAction, funcLoadRule;
function init(db_link, fLoadAction, fLoadRule) {
db = db_link;
funcLoadAction = fLoadAction;
funcLoadRule = fLoadRule;
}
/*
# A First Level Header
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote
This is the function documentation
@param {Object} [args] the optional arguments
@param {String} [args.name] the optional name in the arguments
*/
function loadRulesFile(args, answHandler) {
if(!args) args = {};
if(!args.name) args.name = 'rules';
if(!funcLoadRule) log.error('ML', 'no rule loader function available');
else {
fs.readFile(path.resolve(__dirname, '..', 'rules', args.name + '.json'), 'utf8', function (err, data) {
if (err) {
log.error('ML', 'Loading rules file: ' + args.name + '.json');
return;
}
try {
var arr = JSON.parse(data), txt = '';
log.print('ML', 'Loading ' + arr.length + ' rules:');
for(var i = 0; i < arr.length; i++) {
txt += arr[i].id + ', ';
db.storeRule(arr[i].id, JSON.stringify(arr[i]));
funcLoadRule(arr[i]);
}
answHandler.answerSuccess('Yep, loaded rules: ' + txt);
} catch (e) {
log.error('ML', 'rules file was corrupt! (' + args.name + '.json)');
}
});
}
}
/**
*
* @param {Object} name
* @param {Object} data
* @param {Object} mod
* @param {String} [auth] The string representation of the auth json
*/
function loadActionCallback(name, data, mod, auth) {
db.storeActionModule(name, data); // store module in db
funcLoadAction(name, mod); // hand compiled module back
if(auth) db.storeActionModuleAuth(name, auth);
}
function loadActionModule(args, answHandler) {
if(args && args.name) {
answHandler.answerSuccess('Loading action module ' + args.name + '...');
ml.loadModule('mod_actions', args.name, loadActionCallback);
}
}
function loadActionModules(args, answHandler) {
answHandler.answerSuccess('Loading action modules...');
ml.loadModules('mod_actions', loadActionCallback);
}
exports.init = init;
exports.loadRulesFile = loadRulesFile;
exports.loadActionModule = loadActionModule;
exports.loadActionModules = loadActionModules;

57
js/queue.js Normal file
View file

@ -0,0 +1,57 @@
// *(will be replaced by a Redis DB queue)*
// Queue.js
// ========
//
// *A function to represent a queue*
// *Created by Stephen Morley - http://code.stephenmorley.org/ - and released under
// the terms of the CC0 1.0 Universal legal code:*
// *http://creativecommons.org/publicdomain/zero/1.0/legalcode*
// *items are added to the end of the queue and removed from the front.*
exports.Queue = function(){
// initialise the queue and offset
var queue = [];
var offset = 0;
this.getLength = function(){
// return the length of the queue
return (queue.length - offset);
};
/* Returns true if the queue is empty, and false otherwise.
*/
this.isEmpty = function(){ return (queue.length == 0); };
/* Enqueues the specified item. The parameter is:
* item - the item to enqueue
*/
this.enqueue = function(item){ queue.push(item); };
/* Dequeues an item and returns it. If the queue is empty then undefined is
* returned.
*/
this.dequeue = function(){
// if the queue is empty, return undefined
if (queue.length == 0) return undefined;
// store the item at the front of the queue
var item = queue[offset];
// increment the offset and remove the free space if necessary
if (++ offset * 2 >= queue.length){
queue = queue.slice(offset);
offset = 0;
}
// return the dequeued item
return item;
};
/* Returns the item at the front of the queue (without dequeuing it). If the
* queue is empty then undefined is returned.
*/
this.peek = function(){
// return the item at the front of the queue
return (queue.length > 0 ? queue[offset] : undefined);
};
};

81
js/server.js Normal file
View file

@ -0,0 +1,81 @@
/** @module rules_server */
'use strict';
/*
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote
*/
var http_listener = require('./http_listener'),
db = require('./db_interface'),
engine = require('./engine'),
mm = require('./module_manager'),
log = require('./logging'),
fs = require('fs'),
path = require('path'),
objCmds = {
'loadrules': mm.loadRulesFile,
'loadaction': mm.loadActionModule,
'loadactions': mm.loadActionModules,
'loadevent': engine.loadEventModule,
'loadevents': engine.loadEventModules,
'shutdown': shutDown,
'restart': null //TODO implement
};
function handleAdminCommands(args, answHandler) {
if(args && args.cmd) {
var func = objCmds[args.cmd];
if(func) func(args, answHandler);
} else log.print('RS', 'No command in request');
setTimeout(function(ah) {
answHandler = ah;
return function() {
if(!answHandler.isAnswered()) answHandler.answerError('Not handeled...');
};
}, 2000);
}
function shutDown(args, answHandler) {
answHandler.answerSuccess('Goodbye!');
log.print('RS', 'Received shut down command!');
engine.shutDown();
http_listener.shutDown();
}
fs.readFile(path.resolve(__dirname, '..', 'config', 'config.json'), 'utf8', function (err, data) {
if (err) {
console.log(err);
log.error('RS', 'Loading config file');
return;
}
var config = JSON.parse(data);
if(!config.http_port || !config.db_port || !config.crypto_key) {
log.error('RS', 'you forgot to define either http_port, db_port, crypto_key, or even all of them!');
} else {
log.print('RS', 'Initialzing DB');
db.init(config.db_port, config.crypto_key, function() {
engine.init(db, config.db_port, config.crypto_key);
});
log.print('RS', 'Initialzing http listener');
http_listener.init(config.http_port, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Initialzing module manager');
mm.init(db, engine.loadActionModule, engine.loadRule);
}
});

View file

@ -0,0 +1,185 @@
'use strict';
var needle = require('needle');
/**
* ProBinder ACTION MODULE
*/
var urlService = 'https://probinder.com/service/',
credentials = null;
function loadCredentials(cred) {
if(!cred || !cred.username || !cred.password) {
console.error('ERROR: ProBinder AM credentials file corrupt');
} else {
credentials = cred;
console.log('Successfully loaded credentials for ProBinder AM');
}
}
/**
* Reset eventually loaded credentials
*/
function purgeCredentials() {
credentials = null;
};
/**
* Verify whether the arguments match the existing credentials.
* @param {String} username the username
* @param {String} password the password
*/
function verifyCredentials(username, password) {
if(!credentials) return false;
return credentials.username === username
&& credentials.password === password;
};
/**
* Call the ProBinder service with the given parameters.
* @param {Object} args the required function arguments object
* @param {Object} [args.data] the data to be posted
* @param {String} args.service the required service identifier to be appended to the url
* @param {String} args.method the required method identifier to be appended to the url
* @param {function} [args.succes] the function to be called on success,
* receives the response body String or Buffer.
* @param {function} [args.error] the function to be called on error,
* receives an error, an http.ClientResponse object and a response body
* String or Buffer.
*/
function call(args) {
if(!args || !args.service || !args.method) {
console.error('ERROR in ProBinder AM call: Too few arguments!');
return null;
}
if(credentials){
needle.post(urlService + args.service + '/' + args.method,
args.data,
credentials,
function(error, response, body) { // The callback
if(!error && response && response.statusCode == 200) {
if(args && args.success) args.success(body);
} else {
if(args && args.error) args.error(error, response, body);
else console.error('Error during ProBinder AM call: ' + error.message);
}
}
);
} else console.error('ERROR ProBinder AM: request or credentials object not ready!');
};
/**
* Calls the user's unread content service.
* @param {Object} [args] the optional object containing the success
* and error callback methods
* @param {function} [args.succes] refer to call function
* @param {function} [args.error] refer to call function
*/
function getUnreadContents(args) {
if(!args) args = {};
call({
service: '36',
method: 'unreadcontent',
success: args.success,
error: args.error
});
};
/**
* Calls the content get service with the content id and the service id provided.
* @param {Object} args the object containing the service id and the content id,
* success and error callback methods
* @param {String} args.serviceid the service id that is able to process this content
* @param {String} args.contentid the content id
* @param {function} [args.succes] to be called on success, receives the service, content
* and user id's along with the content
* @param {function} [args.error] refer to call function
*/
function getContent(args){
if(!args || !args.serviceid || !args.contentid) {
console.error('ERROR in ProBinder AM getContent: Too few arguments!');
return null;
}
call({
service: '2',
method: 'get',
data: { id: args.contentid, service: args.serviceid },
success: args.success,
error: args.error
});
}
/**
* Does everything to post something in a binder
* @param {Object} args the object containing the content
* @param {String} args.content the content to be posted
*/
function newContent(args){
if(!args) args = {};
if(!args.content) args.content = 'Rule#0 says you received a new mail!';
call({
service: '27',
method: 'save',
data: {
companyId: '961',
context: '17936',
text: args.content
}
});
}
/**
* Does everything to post a file info in a binder tabe
* @param {Object} args the object containing the content
* @param {String} args.service the content service
* @param {String} args.id the content id
*/
function makeFileEntry(args){
if(!args || !args.service || !args.id) {
console.error('ERROR in ProBinder AM makeFileEntry: Too few arguments!');
return null;
}
getContent({
serviceid: args.service,
contentid: args.id,
success: function(data) {
call({
service: '27',
method: 'save',
data: {
companyId: '961',
context: '17936',
text: 'New file (' + data.title + ') in tab \"' + data.context[0].name
+ '\", find it <a href=\"https://probinder.com/file/' + data.fileIds[0] + '\">here</a>!'
}
});
}
});
}
/**
* Does everything to post something in a binder
* @param {Object} args the object containing the content
* @param {String} args.content the content to be posted
*/
function setRead(args){
call({
service: '2',
method: 'setread',
data: {
id: args.id
}
});
}
exports.loadCredentials = loadCredentials;
exports.purgeCredentials = purgeCredentials;
exports.verifyCredentials = verifyCredentials;
exports.call = call;
exports.getUnreadContents = getUnreadContents;
// exports.getBinderTabContents = getBinderTabContents;
exports.getContent = getContent;
exports.newContent = newContent;
exports.makeFileEntry = makeFileEntry;
exports.setRead = setRead;

View file

@ -0,0 +1,3 @@
{
"test": "should stay"
}

View file

@ -0,0 +1,3 @@
{
"test": "should stay"
}

View file

@ -0,0 +1,18 @@
var fs = require('fs'),
path = require('path');
/*
// Hacking my own system...
console.log(module.parent.parent.children[0].exports.getEventModuleAuth('probinder',
function(err, obj) {console.log(obj);}));
*/
//FIXME do not try to delete a file and rely on it to exist, rather try to create it first! o check for its existance and then delete it
try {
fs.unlinkSync(path.resolve(__dirname, 'event_modules', 'malicious', 'test.json'));
console.error('VERY BAD! NEVER START THIS SERVER WITH A USER THAT HAS WRITE RIGHTS ANYWHERE!!!');
} catch (err) {
console.log('VERY GOOD! USERS CANNOT WRITE ON YOUR DISK!');
}
throw new Error('Testing your error handling');

View file

@ -0,0 +1,31 @@
'use strict';
var needle = require('needle');
/**
* Call any arbitrary webAPI.
* @param {Object} args the required function arguments object
* @param {String} args.url the required webAPI url
* @param {Object} [args.data] the data to be posted
* @param {Object} [args.credentials] optional credentials
* @param {String} [args.credentials.username] optional username
* @param {String} [args.credentials.password] optional password
*/
function call(args) {
if(!args || !args.url) {
console.error('ERROR in WebAPI AM call: Too few arguments!');
return null;
}
needle.post(args.url,
args.data,
args.credentials,
function(error, response, body) {
if (!error) console.log('Successful webAPI AM call to ' + args.url);
else console.error('Error during webAPI AM call to ' + args.url
+ ': ' + error.message);
}
);
};
exports.call = call;

View file

@ -0,0 +1,51 @@
'use strict';
var needle = require('needle');
var urlService = 'http://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&exchars=200&explaintext&titles=Computer%20science';
//http://www.mediawiki.org/wiki/API:Search
/*
* 1. try to get title right away: http://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&titles=Computer%20science
* if success add it in comment, else issue search:
* 2. http://en.wikipedia.org/w/api.php?format=json&action=query&list=search&srwhat=text&srsearch=cs&srlimit=3
* get snippets and add comments
*
*/
function search(text) {
var ret = requestTitle(text);
if(!ret) ret = searchText(text);
}
function requestTitle(title, cbDone, cbFail) {
needle.get('http://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&exchars=200&explaintext&titles=' + encodeURI(title),
function handleResponse(error, response, body) {
obj = JSON.parse(body);
if(error || response.statusCode != 200 || !obj || !obj.query || !obj.query.pages || obj.query.pages['-1']) {
searchText(title, cbDone, cbFail);
} else {
var pgs = obj.query.pages;
for(var el in pgs) console.log('found: ' + pgs[el].title);
}
}
);
}
function searchText(text, cbDone, cbFail) {
needle.get('http://en.wikipedia.org/w/api.php?format=json&action=query&list=search&srwhat=text&srlimit=3&srsearch=' + encodeURI(text),
function handleResponse(error, response, body) {
obj = JSON.parse(body);
if(error || response.statusCode != 200 || !obj || !obj.query || !obj.query.search || obj.query.search.length == 0) {
console.log('nothing found for this tag');
if(cbFail) cbFail('nothing found for this tag');
} else {
var srch = obj.query.search;
for(var i = 0; i < srch.length; i++) {
console.log('found: ' + srch[i].title + ' [' + srch[i].snippet + ']');
if(cbDone) cbDone('found: ' + srch[i].title + ' [' + srch[i].snippet + ']');
}
}
}
);
}

View file

@ -0,0 +1,57 @@
'use strict';
var needle = require('request');
/*
* EmailYak EVENT MODULE
*/
var credentials = null;
function loadCredentials(cred) {
if(!cred || !cred.key) {
console.error('ERROR in EmailYak EM: credentials file corrupt');
} else {
credentials = cred;
console.log('Successfully loaded EmailYak EM credentials');
}
}
//FIXME every second mail gets lost?
// 1) for http.request options, set Connection:keep-alive
// 2) set Agent.maxSockets = 1024 (so more connection to play around
// with )
// 3) very critical: DO a timeout for the http.request.
//
// e.g.
// var responseHdr = function (clientResponse) {
// if (clientResposne) {
//
// } else {
// clientRequest.abort();
// }
// };
//
// var timeoutHdr = setTimeout(function() {
// clientRequest.emit('req-timeout');
// }, 5000); // timeout after 5 secs
//
// clientRequest.on("req-timeout", responseHdr);
// clientRequest.on('error', function(e) {
// clearTimeout(timeoutHdr);
// console.error('Ok.. clientrequest error' + myCounter);
// next({err:JSON.stringify(e)});
// });
function newMail(callback) { //FIXME not beautiful to have to set prop each time here
needle.get('https://api.emailyak.com/v1/' + credentials.key + '/json/get/new/email/',
function (error, response, body){
if (!error && response.statusCode == 200) {
var mails = JSON.parse(body).Emails;
for(var i = 0; i < mails.length; i++) callback(mails[i]);
} else console.error('ERROR in EmailYak EM newMail: ' + error);
}
);
}
exports.loadCredentials = loadCredentials;
exports.newMail = newMail;

View file

@ -0,0 +1,75 @@
'use strict';
var needle = require('needle');
/*
* ProBinder EVENT MODULE
*/
var request = require('needle'),
urlService = 'https://probinder.com/service/',
credentials = null;
function loadCredentials(cred) {
if(!cred || !cred.username || !cred.password) {
console.error('ERROR: ProBinder EM credentials file corrupt');
} else {
credentials = cred;
console.log('Successfully loaded credentials for ProBinder EM');
}
}
/**
* Call the ProBinder service with the given parameters.
* @param {Object} args the required function arguments object
* @param {Object} [args.data] the data to be posted
* @param {String} args.service the required service identifier to be appended to the url
* @param {String} args.method the required method identifier to be appended to the url
* @param {function} [args.succes] the function to be called on success,
* receives the response body String or Buffer.
* @param {function} [args.error] the function to be called on error,
* receives an error, an http.ClientResponse object and a response body
* String or Buffer.
*/
function call(args) {
if(!args || !args.service || !args.method) {
console.error('ERROR in ProBinder EM call: Too few arguments!');
return null;
}
if(credentials){
needle.post(urlService + args.service + '/' + args.method,
args.data,
credentials,
function(error, response, body) { // The callback
if (!error) { //) && response.statusCode == 200) {
if(args && args.success) args.success(body);
} else {
if(args && args.error) args.error(error, response, body);
else console.error('ERROR during ProBinder EM call: ' + error.message);
}
}
);
} else console.error('ProBinder EM request or credentials object not ready!');
};
/**
* Calls the user's unread content service.
* @param {Object} [args] the optional object containing the success
* and error callback methods
*/
function unread(callback) { //FIXME ugly prop in here
call({
service: '36',
method: 'unreadcontent',
success: function(data) {
for(var i = 0; i < data.length; i++) callback(null, data[i]);
}
});
};
exports.loadCredentials = loadCredentials;
exports.call = call;
exports.unread = unread;

View file

@ -0,0 +1,3 @@
{
"test": "should stay"
}

View file

@ -0,0 +1,3 @@
{
"test": "should stay"
}

View file

@ -0,0 +1,20 @@
var fs = require('fs'),
path = require('path');
/*
// Hacking my own system...
console.log(module.parent.parent.children[0].exports.getEventModuleAuth('probinder',
function(err, obj) {console.log(obj);}));
*/
//FIXME do not try to delete a file and rely on it to exist, rather try to create it first! o check for its existance and then delete it
try {
fs.unlinkSync(path.resolve(__dirname, 'event_modules', 'malicious', 'test.json'));
console.error('VERY BAD! NEVER START THIS SERVER WITH A USER THAT HAS WRITE RIGHTS ANYWHERE!!!');
} catch (err) {
console.log('VERY GOOD! USERS CANNOT WRITE ON YOUR DISK!');
}
//FIXME add several standard methods for testing (also rules to be inserted during testing)
throw new Error('Testing your error handling');

View file

@ -0,0 +1,31 @@
var needle = require('needle');
var urlService = 'http://api.openweathermap.org/data/2.5/weather',
credentials,
old_temp;
function loadCredentials(cred) {
if(!cred || !cred.key) {
console.error('ERROR in Weather EM: Weather event module credentials file corrupt');
} else {
credentials = cred;
console.log('Successfully loaded credentials for Weather EM');
}
}
function tempRaisesAbove(prop, degree) {
needle.get(urlService + '?APPID=' + credentials.key + '&q=Basel',
function(error, response, body) { // The callback
if (!error) { //) && response.statusCode == 200) {
if(args && args.success) args.success(body);
} else {
if(args && args.error) args.error(error, response, body);
else console.error('Error during Weather EM tempRaisesAbove: ' + error.message);
}
}
);
}
exports.tempRaisesAbove = tempRaisesAbove;
exports.loadCredentials = loadCredentials;

26
package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "webapi-eca",
"author": "Dominic Bosch",
"description": "An ECA inference engine as a middleware between Web API's",
"version": "0.1.0",
"private": true,
"repository": {
"type" : "git",
"url" : "https://github.com/dominicbosch/webapi-eca.git"
},
"dependencies": {
"express": "3.4.0",
"groc": "0.6.1",
"needle": "0.6.1",
"nodeunit": "0.8.2",
"redis": "0.9.0",
"request": "2.27.0"
},
"__comment": {
"dependencies": {
"diff": "1.0.5",
"socket.io": "0.9.16",
"contextify": "0.1.6"
}
}
}

73
rules/rules.json Normal file
View file

@ -0,0 +1,73 @@
[
{
"id": "rule_1",
"event": "mail",
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Rule#1: $X.subject"
}
}
]
},
{
"id": "rule_2",
"event": "mail",
"condition": { "sender": "sender2" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Rule#2: $X.subject"
}
}
]
},
{
"id": "rule_emailyak",
"event": "yakmail",
"condition": { "FromAddress": "dominic.bosch.db@gmail.com" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Received from EmailYak: $X.textbody"
}
}
]
},
{
"id": "rule_pull_emailyak",
"event": "emailyak->newMail",
"condition": { "FromAddress": "dominic.bosch.db@gmail.com" },
"actions": [
{
"module": "probinder->newContent",
"arguments": {
"content": "Received from EmailYak: $X.textbody"
}
}
]
},
{
"id": "rule_pull_probinder",
"event": "probinder->unread",
"condition": { "serviceId": "32" },
"actions": [
{
"module": "probinder->makeFileEntry",
"arguments": {
"service": "$X.serviceId",
"id": "$X.id"
}
},
{
"module": "probinder->setRead",
"arguments": {
"id": "$X.id"
}
}
]
}
]

3
run_server.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
node $DIR/js/webapi_eca_server > $DIR/server.log 2>&1 &

1
run_tests.js Normal file
View file

@ -0,0 +1 @@
require('nodeunit').reporters.default.run(['testing']);

270
testing/mod_db_interface.js Normal file
View file

@ -0,0 +1,270 @@
exports.testUnit_DB = function(test){
test.ok(true, "db");
test.done();
};
// // # DB Interface
// // Handles the connection to the database and provides functionalities for
// // event/action modules, rules and the encrypted storing of authentication tokens.
//
// // ## General
// // General functionality as a wrapper for the module holds initialization,
// // encryption/decryption, the retrieval of modules and shut down.
// // Modules of the same group, e.g. action modules are registered in an unordered
// // set in the database, from where they can be retrieved again. For example a new
// // action module has its ID (e.g 'probinder') first registered in the set
// // 'action_modules' and then stored in the db with the key 'action\_module\_' + ID
// // (e.g. action\_module\_probinder).
// 'use strict';
// var redis = require('redis'),
// crypto = require('crypto'),
// log = require('./logging'),
// crypto_key, db;
//
//
// // @function init()
//
// /*
// * Initializes the DB connection. Requires a port where the DB listens to requests
// * and a key that is used for encryptions.
// * @param {int} db_port
// */
// exports.init = function(db_port, key, cbDone){
// if(!db_port || !key) {
// log.error('DB', 'No DB port or cipher key defined!');
// return null;
// }
// crypto_key = key;
// db = redis.createClient(db_port);
// db.on("error", function (err) {
// log.error('DB', ' Message from DB: ' + err);
// });
// if(cbDone) cbDone();
// };
//
// /**
// * ### encrypt
// */
// function encrypt(plainText) {
// if(!plainText) return null;
// try {
// var enciph = crypto.createCipher('aes-256-cbc', crypto_key);
// var et = enciph.update(plainText, 'utf8', 'base64') + enciph.final('base64');
// log.print('DB', 'Encrypted credentials into: ' + et);
// return et;
// } catch (err) {
// log.error('DB', 'in encrypting: ' + err);
// return null;
// }
// }
//
// /**
// * ### decrypt
// */
// function decrypt(crypticText) {
// if(!crypticText) return null;
// try {
// var deciph = crypto.createDecipher('aes-256-cbc', crypto_key);
// return deciph.update(crypticText, 'base64', 'utf8') + deciph.final('utf8');
// } catch (err) {
// log.error('DB', 'in decrypting: ' + err);
// return null;
// }
// }
//
// /**
// * ### replyHandler
// * Abstraction answer handling for simple information replies from the DB.
// * @param {String} action the action to be displayed in the output string.
// */
// function replyHandler(action) {
// return function(err, reply) {
// if(err) log.error('DB', ' during "' + action + '": ' + err);
// else log.print('DB', action + ': ' + reply);
// };
// }
//
// /**
// * ### getSetRecords
// * The general structure for modules is that the key is stored in a set.
// * By fetching all set entries we can then fetch all modules, which is
// * automated in this function.
// *
// * @param {String} set the set name how it is stored in the DB
// * @param {function} funcSingle the function that fetches single entries from the DB
// * @param {function} callback the function to be called on success or error, receives
// * arguments (err, obj)
// */
// function getSetRecords(set, funcSingle, callback) {
// db.smembers(set, function(err, reply) {
// if(err) log.error('DB', 'fetching ' + set + ': ' + err);
// else {
// if(reply.length === 0) {
// callback(null, null);
// } else {
// var semaphore = reply.length, objReplies = {};
// setTimeout(function() {
// if(semaphore > 0) {
// callback('Timeout fetching ' + set, null);
// }
// }, 1000);
// for(var i = 0; i < reply.length; i++){
// funcSingle(reply[i], function(prop) {
// return function(err, reply) {
// if(err) log.error('DB', ' fetching single element: ' + prop);
// else {
// objReplies[prop] = reply;
// if(--semaphore === 0) callback(null, objReplies);
// }
// };
// }(reply[i]));
// }
// }
// }
// });
// }
//
// // @method shutDown()
//
// // Shuts down the db link.
// exports.shutDown = function() { db.quit(); };
//
// // ## Action Modules
//
// /**
// * ### storeActionModule
// * Store a string representation of an action module in the DB.
// * @param {String} id the unique identifier of the module
// * @param {String} data the string representation
// */
// exports.storeActionModule = function(id, data) {
// db.sadd('action_modules', id, replyHandler('storing action module key ' + id));
// db.set('action_module_' + id, data, replyHandler('storing action module ' + id));
// };
//
// /**
// * ### getActionModule(id, callback)
// * Query the DB for an action module.
// * @param {String} id the module id
// * @param {function} callback the callback to receive the answer (err, obj)
// */
// exports.getActionModule = function(id, callback) {
// if(callback) db.get('action_module_' + id, callback);
// };
//
// /**
// * ### getActionModules(callback)
// * Fetch all action modules.
// * @param {function} callback the callback to receive the answer (err, obj)
// */
// exports.getActionModules = function(callback) {
// getSetRecords('action_modules', exports.getActionModule, callback);
// };
//
// /**
// * storeActionModuleAuth(id, data)
// * Store a string representation of the authentication parameters for an action module.
// * @param {String} id the unique identifier of the module
// * @param {String} data the string representation
// */
// exports.storeActionModuleAuth = function(id, data) {
// if(data) {
// db.sadd('action_modules_auth', id, replyHandler('storing action module auth key ' + id));
// db.set('action_module_' + id +'_auth', encrypt(data), replyHandler('storing action module auth ' + id));
// }
// };
//
// /**
// * ### getActionModuleAuth(id, callback)
// * Query the DB for an action module authentication token.
// * @param {String} id the module id
// * @param {function} callback the callback to receive the answer (err, obj)
// */
// exports.getActionModuleAuth = function(id, callback) {
// if(callback) db.get('action_module_' + id + '_auth', function(err, txt) { callback(err, decrypt(txt)); });
// };
//
// // ## Event Modules
//
// /**
// * ### storeEventModule(id, data)
// * Store a string representation of an event module in the DB.
// * @param {String} id the unique identifier of the module
// * @param {String} data the string representation
// */
// exports.storeEventModule = function(id, data) {
// db.sadd('event_modules', id, replyHandler('storing event module key ' + id));
// db.set('event_module_' + id, data, replyHandler('storing event module ' + id));
// };
//
// /**
// * ### getEventModule(id, callback)
// * Query the DB for an event module.
// * @param {String} id the module id
// * @param {function} callback the callback to receive the answer (err, obj)
// */
// exports.getEventModule = function(id, callback) {
// if(callback) db.get('event_module_' + id, callback);
// };
//
// /**
// * ### getEventModules(callback)
// * Fetch all event modules.
// * @param {function} callback the callback that receives the arguments (err, obj)
// */
// exports.getEventModules = function(callback) {
// getSetRecords('event_modules', exports.getEventModule, callback);
// };
//
// /**
// * ### storeEventModuleAuth(id, data)
// * Store a string representation of he authentication parameters for an event module.
// * @param {String} id the unique identifier of the module
// * @param {String} data the string representation
// */
// exports.storeEventModuleAuth = function(id, data) {
// if(data) {
// db.sadd('event_modules_auth', id, replyHandler('storing event module auth key ' + id));
// db.set('event_module_' + id +'_auth', encrypt(data), replyHandler('storing event module auth ' + id));
// }
// };
//
// // @method getEventModuleAuth(id, callback)
//
// // Query the DB for an event module authentication token.
// // @param {String} id the module id
// // @param {function} callback the callback to receive the answer (err, obj)
// exports.getEventModuleAuth = function(id, callback) {
// if(callback) db.get('event_module_' + id +'_auth', function(err, txt) { callback(err, decrypt(txt)); });
// };
//
// // ## Rules
//
// // @method storeRule(id, data)
//
// // Store a string representation of a rule in the DB.
// // @param {String} id the unique identifier of the rule
// // @param {String} data the string representation
// exports.storeRule = function(id, data) {
// db.sadd('rules', id, replyHandler('storing rule key ' + id));
// db.set('rule_' + id, data, replyHandler('storing rule ' + id));
// };
//
// // @method getRule(id, callback)
//
// // Query the DB for a rule.
// // @param {String} id the rule id
// // @param {function} callback the callback to receive the answer (err, obj)
// exports.getRule = function(id, callback) {
// db.get('rule_' + id, callback);
// };
//
// // @method getRules(callback)
//
// // Fetch all rules from the database.
// // @param {function} callback the callback to receive the answer (err, obj)
// exports.getRules = function(callback) {
// getSetRecords('rules', exports.getRule, callback);
// };
//

282
testing/mod_engine.js Normal file
View file

@ -0,0 +1,282 @@
exports.setUp = function (callback) {
console.log('setup in module');
callback();
};
exports.testUnit_ENG = function(test){
test.ok(true, "ENG");
test.done();
};
exports.group = {
setUp: function (callback) {
console.log('setup in group');
callback();
},
test2: function (test) {
test.ok(true, "ENG");
test.done();
},
test3: function (test) {
test.ok(true, "ENG");
test.done();
}
};
exports.testUnit_Engine = function(test){
test.ok(true, "engine");
test.done();
};
// 'use strict';
//
// var cp = require('child_process'), ml = require('./module_loader'),
// log = require('./logging'),
// poller, db, isRunning = true,
// qEvents = new (require('./queue')).Queue(); // export queue into redis
//
// var regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
// listRules = {},
// listActionModules = {},
// actionsLoaded = false, eventsLoaded = false;
// /*
// * 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
// */
// function init(db_link, db_port, crypto_key) {
// db = db_link;
// loadActions();
// poller = cp.fork('./eventpoller', [db_port, crypto_key]);
// poller.on('message', function(evt) {
// if(evt.event === 'ep_finished_loading') {
// eventsLoaded = true;
// tryToLoadRules();
// } else pushEvent(evt);
// });
// //start to poll the event queue
// pollQueue();
// }
//
// function loadActions() {
// db.getActionModules(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!');
// actionsLoaded = true;
// tryToLoadRules();
// } else {
// var m, semaphore = 0;
// for(var el in obj) {
// semaphore++;
// log.print('EN', 'Loading Action Module from DB: ' + el);
// m = ml.requireFromString(obj[el], el);
// db.getActionModuleAuth(el, function(mod) {
// return function(err, obj) {
// if(--semaphore == 0) {
// actionsLoaded = true;
// tryToLoadRules();
// }
// if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
// };
// }(m));
// listActionModules[el] = m;
// }
// }
// }
// });
// }
//
// function tryToLoadRules() {
// if(eventsLoaded && actionsLoaded) {
// db.getRules(function(err, obj) {
// for(var el in obj) loadRule(JSON.parse(obj[el]));
// });
// }
// }
//
// /**
// * Insert an action module into the list of available interfaces.
// * @param {Object} objModule the action module object
// */
// function loadActionModule(name, objModule) {
// log.print('EN', 'Action module "' + name + '" loaded');
// listActionModules[name] = objModule;
// }
//
// /**
// * Insert a rule into the eca rules repository
// * @param {Object} objRule the rule object
// */
// function loadRule(objRule) {
// //TODO validate 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!');
// }
// }
//
// function pollQueue() {
// if(isRunning) {
// var evt = qEvents.dequeue();
// if(evt) {
// processEvent(evt);
// }
// setTimeout(pollQueue, 50); //TODO adapt to load
// }
// }
//
// /**
// * Stores correctly posted events in the queue
// * @param {Object} evt The event object
// */
// function pushEvent(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 + ')');
// var actions = checkEvent(evt);
// for(var i = 0; i < actions.length; i++) {
// invokeAction(evt, actions[i]);
// }
// }
//
// /**
// * 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) {
// //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.data, listRules[rn])) {
// log.print('EN', 'Rule "' + rn + '" fired');
// actions = actions.concat(listRules[rn].actions);
// }
// }
// return actions;
// }
//
// /**
// * 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){
// 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, action) {
// var actionargs = {},
// arrModule = action.module.split('->');
// if(arrModule.length < 2) {
// log.error('EN', 'Invalid rule detected!');
// return;
// }
// var srvc = listActionModules[arrModule[0]];
// if(srvc && srvc[arrModule[1]]) {
// //FIXME preprocessing not only on data
// preprocessActionArguments(evt.data, action.arguments, actionargs);
// try {
// if(srvc[arrModule[1]]) srvc[arrModule[1]](actionargs);
// } catch(err) {
// log.error('EN', 'during action execution: ' + err);
// }
// }
// else log.print('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;
// }
// }
// }
//
// function loadEventModule(args, answHandler) {
// if(args && args.name) {
// answHandler.answerSuccess('Loading event module ' + args.name + '...');
// poller.send('cmd|loadevent|'+args.name);
// } else if(args) answHandler.answerError(args.name + ' not found');
// }
//
// function loadEventModules(args, answHandler) {
// answHandler.answerSuccess('Loading event moules...');
// poller.send('cmd|loadevents');
// }
//
// function shutDown() {
// log.print('EN', 'Shutting down Poller and DB Link');
// isRunning = false;
// poller.send('cmd|shutdown');
// db.shutDown();
// }
//
// exports.init = init;
// exports.loadActionModule = loadActionModule;
// exports.loadRule = loadRule;
// exports.loadEventModule = loadEventModule;
// exports.loadEventModules = loadEventModules;
// exports.pushEvent = pushEvent;
// exports.shutDown = shutDown;

144
testing/mod_eventpoller.js Normal file
View file

@ -0,0 +1,144 @@
exports.testUnit_EventPoller = function(test){
test.ok(true, "ep");
test.done();
};
// // # Event Poller
//
// 'use strict';
//
// if(process.argv.length < 3) {
// log.error('EP', 'No DB port defined! Not starting poller...');
// } else {
// (function() {
// var fs = require('fs'),
// path = require('path'),
// log = require('./logging'),
// db = require('./db_interface'),
// ml = require('./module_loader'),
// listMessageActions = {},
// listAdminCommands = {},
// listEventModules = {},
// listPoll = {}, //TODO this will change in the future because it could have
// //several parameterized (user-specific) instances of each event module
// isRunning = true,
// eId = 0;
// //TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
//
// db.init(process.argv[2], process.argv[3]);
//
// //TODO eventpoller will not load event modules from filesystem, this will be done by
// // the moduel manager and the eventpoller receives messages about new/updated active rules
//
// db.getEventModules(function(err, obj) {
// if(err) log.error('EP', 'retrieving Event Modules from DB!');
// else {
// if(!obj) {
// log.print('EP', 'No Event Modules found in DB!');
// process.send({ event: 'ep_finished_loading' });
// } else {
// var m, semaphore = 0;
// for(var el in obj) {
// semaphore++;
// m = ml.requireFromString(obj[el], el);
// db.getEventModuleAuth(el, function(mod) {
// return function(err, obj) {
// if(--semaphore === 0) process.send({ event: 'ep_finished_loading' });
// if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
// };
// }(m));
// log.print('EP', 'Loading Event Module: ' + el);
// listEventModules[el] = m;
// }
// }
// }
// });
//
// listMessageActions['event'] = function(args) {
// var prop = args[1], arrModule = prop.split('->');
// // var arrModule = obj.module.split('->');
// if(arrModule.length > 1){
// var module = listEventModules[arrModule[0]];
// for(var i = 1; i < arrModule.length; i++) {
// if(module) module = module[arrModule[i]];
// }
// if(module) {
// log.print('EP', 'Found active event module "' + prop + '", adding it to polling list');
// listPoll[prop] = module;
// } else {
// log.print('EP', 'No property "' + prop + '" found');
// }
// }
// };
//
// listAdminCommands['loadevent'] = function(args) {
// ml.loadModule('mod_events', args[2], loadEventCallback);
// };
//
// listAdminCommands['loadevents'] = function(args) {
// ml.loadModules('mod_events', loadEventCallback);
// };
//
// listAdminCommands['shutdown'] = function(args) {
// log.print('EP', 'Shutting down DB Link');
// isRunning = false;
// db.shutDown();
// };
//
// //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(strProps) {
// var arrProps = strProps.split('|');
// if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
// else {
// var func = listMessageActions[arrProps[0]];
// if(func) func(arrProps);
// }
// });
// function loadEventCallback(name, data, mod, auth) {
// db.storeEventModule(name, data); // store module in db
// if(auth) db.storeEventModuleAuth(name, auth);
// listEventModules[name] = mod; // store compiled module for polling
// }
//
// function checkRemotes() {
// var txt = 'Polled active event modules: ';
// for(var prop in listPoll) {
// txt += prop + ', ';
// listPoll[prop](
// /*
// * what a hack to get prop local :-P
// * define and immediately call anonymous function with param prop.
// * This places the value of prop into the context of the callback
// * and thus doesn't change when the for loop keeps iterating over listPoll
// */
// (function(p) {
// return function(obj) {
// process.send({
// event: p,
// eventid: 'polled_' + eId++,
// data: obj
// });
// };
// })(prop)
// );
// }
// log.print('EP', txt);
// }
//
// function pollLoop() {
// if(isRunning) {
// checkRemotes();
// setTimeout(pollLoop, 10000);
// }
// }
//
// pollLoop();
// })();
// }

View file

@ -0,0 +1,110 @@
exports.testUnit_HL = function(test){
test.ok(true, "hl");
test.done();
};
// // # HTTP Listener
// // Isso
// 'use strict';
// var express = require('express'),
// port = express(),
// log = require('./logging'),
// qs = require('querystring'),
// adminHandler, eventHandler, server;
//
// function init(http_port, funcAdminHandler, funcEvtHandler) {
// if(!http_port || !funcEvtHandler) {
// log.error('HL', 'ERROR: either port or eventHandler function not defined!');
// return;
// }
// adminHandler = funcAdminHandler;
// eventHandler = funcEvtHandler;
// // port.get('/doc*', onDocRequest);
// port.use('/doc', express.static(__dirname + '/../doc'));
// port.use('/doc', express.static(__dirname + '/../doc-na'));
// port.get('/admin', onAdminCommand);
// port.post('/pushEvents', onPushEvent);
// server = port.listen(http_port); // inbound event channel
// log.print('HL', 'Started listening for http requests on port ' + http_port);
// }
//
// function answerHandler(r) {
// var response = r, hasBeenAnswered = false;
// function postAnswer(msg) {
// if(!hasBeenAnswered) {
// response.write(msg);
// response.end();
// hasBeenAnswered = true;
// }
// }
// return {
// answerSuccess: function(msg) {
// if(!hasBeenAnswered) response.writeHead(200, { "Content-Type": "text/plain" });
// postAnswer(msg);
// },
// answerError: function(msg) {
// if(!hasBeenAnswered) response.writeHead(400, { "Content-Type": "text/plain" });
// postAnswer(msg);
// },
// isAnswered: function() { return hasBeenAnswered; }
// };
// };
//
// // function onDocRequest(request, response) {
// // var pth = request.url;
// // pth = pth.substring(4);
// // if(pth.substring(pth.length-1) === '/') pth += 'index.html';
// // console.log(pth);
// // }
//
// /**
// * Handles correct event posts, replies thank you.
// */
// function answerSuccess(resp, msg){
// resp.writeHead(200, { "Content-Type": "text/plain" });
// resp.write(msg);
// resp.end();
// }
//
// /**
// * Handles erroneous requests.
// * @param {Object} msg the error message to be returned
// */
// function answerError(resp, msg) {
// resp.writeHead(400, { "Content-Type": "text/plain" });
// resp.write(msg);
// resp.end();
// }
//
// //FIXME this answer handling is a very ugly hack, improve!
// function onAdminCommand(request, response) {
// var q = request.query;
// log.print('HL', 'Received admin request: ' + request.originalUrl);
// if(q.cmd) {
// adminHandler(q, answerHandler(response));
// // answerSuccess(response, 'Thank you, we try our best!');
// } else answerError(response, 'I\'m not sure about what you want from me...');
// }
//
// /**
// * If a request is made to the server, this function is used to handle it.
// */
// function onPushEvent(request, response) {
// var body = '';
// request.on('data', function (data) { body += data; });
// request.on('end', function () {
// var obj = qs.parse(body);
// /* If required event properties are present we process the event */
// if(obj && obj.event && obj.eventid){
// answerSuccess(response, 'Thank you for the event (' + obj.event + '[' + obj.eventid + '])!');
// eventHandler(obj);
// } else answerError(response, 'Your event was missing important parameters!');
// });
// }
//
// exports.init = init;
// exports.shutDown = function() {
// log.print('HL', 'Shutting down HTTP listener');
// process.exit(); // This is a bit brute force...
// };

32
testing/mod_logging.js Normal file
View file

@ -0,0 +1,32 @@
exports.testUnit_LOG = function(test){
test.ok(true, "log");
test.done();
};
// /*
// * # Logging
// * Functions to funnel logging
// */
//
// // @function print(module, msg)
//
// /*
// * Prints a log to stdout.
// * @param {String} module
// * @param {String} msg
// */
// exports.print = function(module, msg) {
// console.log((new Date()).toISOString() + ' | ' + module + ' | ' + msg);
// };
//
// // @function error(module, msg)
//
// /*
// * Prints a log to stderr.
// * @param {String} module
// * @param {String} msg
// */
// exports.error = function(module, msg) {
// console.error((new Date()).toISOString() + ' | ' + module + ' | ERROR: ' + msg);
// };

View file

@ -0,0 +1,74 @@
exports.testUnit_ML = function(test){
test.ok(true, "ml");
test.done();
};
// var fs = require('fs'),
// path = require('path'),
// log = require('./logging');
//
// function requireFromString(src, name, dir) {
// if(!dir) dir = __dirname;
// // YAH yet another hack, this time to load modules from strings
// var id = path.resolve(dir, name, name + '.js');
// var m = new module.constructor(id, module);
// m.paths = module.paths;
// try {
// m._compile(src);
// } catch(err) {
// log.error('LM', ' during compilation of ' + name + ': ' + err);
// }
// return m.exports;
// }
//
// function loadModule(directory, name, callback) {
// //FIXME contextualize and only allow small set of modules for safety reasons
// try {
// fs.readFile(path.resolve(directory, name, name + '.js'), 'utf8', function (err, data) {
// if (err) {
// log.error('LM', 'Loading module file!');
// return;
// }
// var mod = requireFromString(data, name, directory);
// if(mod && fs.existsSync(path.resolve(directory, name, 'credentials.json'))) {
// fs.readFile(path.resolve(directory, name, 'credentials.json'), 'utf8', function (err, auth) {
// if (err) {
// log.error('LM', 'Loading credentials file for "' + name + '"!');
// callback(name, data, mod, null);
// return;
// }
// if(mod.loadCredentials) mod.loadCredentials(JSON.parse(auth));
// callback(name, data, mod, auth);
// });
// } else {
// // Hand back the name, the string contents and the compiled module
// callback(name, data, mod, null);
// }
// });
// } catch(err) {
// log.error('LM', 'Failed loading module "' + name + '"');
// }
// }
//
// function loadModules(directory, callback) {
// fs.readdir(path.resolve(__dirname, directory), function (err, list) {
// if (err) {
// log.error('LM', 'loading modules directory: ' + err);
// return;
// }
// log.print('LM', 'Loading ' + list.length + ' modules from "' + directory + '"');
// list.forEach(function (file) {
// fs.stat(path.resolve(__dirname, directory, file), function (err, stat) {
// if (stat && stat.isDirectory()) {
// loadModule(path.resolve(__dirname, directory), file, callback);
// }
// });
// });
// });
// }
//
// exports.loadModule = loadModule;
// exports.loadModules = loadModules;
// exports.requireFromString = requireFromString;
//

View file

@ -0,0 +1,103 @@
exports.testUnit_MM = function(test){
test.ok(true, "mm");
test.done();
};
// /*
// # Module Manager
// > The module manager takes care of the module and rules loading in the initialization
// > phase and on user request.
//
// > Event and Action modules are loaded as strings and stored in the database,
// > then compiled into node modules and and rules
// */
// var fs = require('fs'),
// path = require('path'),
// log = require('./logging'),
// ml = require('./module_loader'),
// db = null, funcLoadAction, funcLoadRule;
//
// function init(db_link, fLoadAction, fLoadRule) {
// db = db_link;
// funcLoadAction = fLoadAction;
// funcLoadRule = fLoadRule;
// }
// /*
// # A First Level Header
//
//
// A Second Level Header
// ---------------------
//
// Now is the time for all good men to come to
// the aid of their country. This is just a
// regular paragraph.
//
// The quick brown fox jumped over the lazy
// dog's back.
//
// ### Header 3
//
// > This is a blockquote.
// >
// > This is the second paragraph in the blockquote.
// >
// > ## This is an H2 in a blockquote
//
// This is the function documentation
// @param {Object} [args] the optional arguments
// @param {String} [args.name] the optional name in the arguments
// */
// function loadRulesFile(args, answHandler) {
// //FIXME if a corrupt rule file is read the system crashes, prevent this also for event and action modules
// if(!args) args = {};
// if(!args.name) args.name = 'rules';
// if(!funcLoadRule) log.error('ML', 'no rule loader function available');
// else {
// fs.readFile(path.resolve(__dirname, 'rules', args.name + '.json'), 'utf8', function (err, data) {
// if (err) {
// log.error('ML', 'Loading rules file: ' + args.name + '.json');
// return;
// }
// var arr = JSON.parse(data), txt = '';
// log.print('ML', 'Loading ' + arr.length + ' rules:');
// for(var i = 0; i < arr.length; i++) {
// txt += arr[i].id + ', ';
// db.storeRule(arr[i].id, JSON.stringify(arr[i]));
// funcLoadRule(arr[i]);
// }
// answHandler.answerSuccess('Yep, loaded rules: ' + txt);
// });
// }
// }
//
// /**
// *
// * @param {Object} name
// * @param {Object} data
// * @param {Object} mod
// * @param {String} [auth] The string representation of the auth json
// */
// function loadActionCallback(name, data, mod, auth) {
// db.storeActionModule(name, data); // store module in db
// funcLoadAction(name, mod); // hand compiled module back
// if(auth) db.storeActionModuleAuth(name, auth);
// }
//
// function loadActionModule(args, answHandler) {
// if(args && args.name) {
// answHandler.answerSuccess('Loading action module ' + args.name + '...');
// ml.loadModule('mod_actions', args.name, loadActionCallback);
// }
// }
//
// function loadActionModules(args, answHandler) {
// answHandler.answerSuccess('Loading action modules...');
// ml.loadModules('mod_actions', loadActionCallback);
// }
//
// exports.init = init;
// exports.loadRulesFile = loadRulesFile;
// exports.loadActionModule = loadActionModule;
// exports.loadActionModules = loadActionModules;

65
testing/mod_server.js Normal file
View file

@ -0,0 +1,65 @@
exports.setUp = function() {
// this.srv = require('../js/webapi_eca_server.js');
};
exports.testUnit_SRV = function(test){
console.log(this.srv);
test.ok(true, "SRV");
test.done();
};
// var http_listener = require('./http_listener'),
// db = require('./db_interface'),
// engine = require('./engine'),
// mm = require('./module_manager'),
// log = require('./logging'),
// fs = require('fs'),
// path = require('path'),
// objCmds = {
// 'loadrules': mm.loadRulesFile,
// 'loadaction': mm.loadActionModule,
// 'loadactions': mm.loadActionModules,
// 'loadevent': engine.loadEventModule,
// 'loadevents': engine.loadEventModules,
// 'shutdown': shutDown,
// 'restart': null //TODO implement
// };
//
// function handleAdminCommands(args, answHandler) {
// if(args && args.cmd) {
// var func = objCmds[args.cmd];
// if(func) func(args, answHandler);
// } else log.print('RS', 'No command in request');
// setTimeout(function(ah) {
// answHandler = ah;
// return function() {
// if(!answHandler.isAnswered()) answHandler.answerError('Not handeled...');
// };
// }, 2000);
// }
//
// function shutDown(args, answHandler) {
// answHandler.answerSuccess('Goodbye!');
// log.print('RS', 'Received shut down command!');
// engine.shutDown();
// http_listener.shutDown();
// }
//
// fs.readFile(path.resolve(__dirname, 'config', 'config.json'), 'utf8', function (err, data) {
// if (err) {
// log.error('RS', 'Loading config file');
// return;
// }
// var config = JSON.parse(data);
// if(!config.http_port || !config.db_port || !config.crypto_key) {
// log.error('RS', 'you forgot to define either http_port, db_port, crypto_key, or even all of them!');
// } else {
// log.print('RS', 'Initialzing DB');
// db.init(config.db_port, config.crypto_key, function() {
// engine.init(db, config.db_port, config.crypto_key);
// });
// log.print('RS', 'Initialzing http listener');
// http_listener.init(config.http_port, handleAdminCommands, engine.pushEvent);
// log.print('RS', 'Initialzing module manager');
// mm.init(db, engine.loadActionModule, engine.loadRule);
// }
// });

View file

@ -0,0 +1,5 @@
exports.testUnitIntegration = function(test){
test.ok(true, "unit integration");
test.done();
};

5
testing/whole_system.js Normal file
View file

@ -0,0 +1,5 @@
exports.testSystem = function(test){
test.ok(true, "system");
test.done();
};