scalra
Version:
node.js framework to prototype and scale rapidly
380 lines (317 loc) • 9.8 kB
JavaScript
//
// notify.js
//
// generic notification service based on 3rd party messaging (email, SMS, ... etc.)
//
// functions
//
// history:
// 2014-02-20 init
//
// cache reference of accounts
var l_accounts = SR.State.get('_accountMap');
//
// API
//
// type: 'email', 'SMS', 'FTP' 'sound'
// send a message to a given service type
exports.send = function (type, data, onDone) {
}
var l_method_table = {
SMS : function(info, user) {
var onDone = function (err, MIR) {
if (err) {
LOG.warn(err.toString(), 'SR.Notify');
}
else {
LOG.warn(MIR, 'SR.Notify');
}
};
SR.Notify.sendSMS("+" + user.data.phone, info.msg, onDone);
},
email : function(info, user) {
var content = {
to : user.email,
subject : "[ " + info.name + " ]",
text : info.msg
};
UTIL.emailText(content);
},
client : function(info, user) {
}
};
exports.customizeMethod = function (name, cb) {
if (typeof cb === "function") {
l_method_table[name] = cb;
return true;
}
else {
return false;
}
};
//ex. SR.Notify.register('critical', ['SMS','email','phone'], 'johnwu', onDone);
exports.register = function (level, methods, account, cb) {
if (arguments.length === 3) {
cb = account;
var clt = SR.DB.getCollection(SR.Settings.DB_NAME_ACCOUNT, function(){});
var fields = {};
fields["data.notification." + "lv_" + level + "_methods"] = {SMS : false, email : false, client : false};
for (var i = 0; i < methods.length; i++) {
fields["data.notification." + "lv_" + level + "_methods"][methods[i]] = true;
}
var update_cb = function (db_err) {
if (db_err) {
var err = new Error("updating DB fail");
err.name = "register Error";
UTIL.safeCall(cb, err);
}
else {
UTIL.safeCall(cb, null);
}
};
clt.update({}, {$set : fields}, {multi : true}, update_cb);
}
else
if (arguments.length === 4) {
var getUserDone = function (getUser_err, customizable_user_data) {
if (getUser_err) {
var err = new Error(getUser_err.toString());
err.name = "register Error";
UTIL.safeCall(cb, err);
}
else {
if (customizable_user_data.notification === undefined) {
customizable_user_data.notification = {};
}
customizable_user_data.notification["lv_" + level + "_methods"] = {SMS : false, email : false, client : false};
for (var i = 0; i < methods.length; i++) {
customizable_user_data.notification["lv_" + level + "_methods"][methods[i]] = true;
}
var setUserDone = function (setUser_err) {
if (setUser_err) {
var err = new Error(setUser_err.toString());
err.name = "register Error";
UTIL.safeCall(cb, err);
}
else {
UTIL.safeCall(cb, null);
}
};
var user = l_accounts[account];
user.data = UTIL.mixin(user.data, customizable_user_data);
//user.data.setUser(account, customizable_user_data, cb);
user.sync(cb);
}
};
var user = l_accounts[account];
getUserDone(null, user.data);
//SR.User.getUser(account, getUserDone);
}
};
exports.alert = function (name, info, level) {
info.name = name;
//LOG.warn('----wake alert----');
SR.Comm.publish(level, {level : level, event : name, msg : info.msg}, "SR_NOTIFY");
var onSuccess = function (users) {
for (var i = 0; i < users.length; i++) {
for (var m in users[i].data.notification["lv_" + level + "_methods"]) {
if (users[i].data.notification["lv_" + level + "_methods"][m] === true) {
if (l_method_table[m]) {
l_method_table[m](info, users[i]);
}
else {
LOG.error("notification method \"" + m + "\" is undefined", 'SR.Notify');
}
}
}
}
};
var onFail = function () {
};
var query = {};
query["data.notification." + "lv_" + level + "_methods"] = {$exists : true};
SR.DB.getArray(SR.Settings.DB_NAME_ACCOUNT, onSuccess, onFail, query);
};
exports.subscribe = function (account, connection, cb) {
var getUserDone = function (getUser_err, customizable_user_data) {
if (getUser_err) {
var err = new Error(getUser_err.toString());
err.name = "subscribe Error";
UTIL.safeCall(cb, err);
}
else {
if (customizable_user_data.notification) {
for (var level in customizable_user_data.notification) {
if (customizable_user_data.notification[level]["client"] === true) {
SR.Comm.subscribe(connection.connID, level.replace("lv_", "").replace("_methods", ""), connection);
}
}
}
UTIL.safeCall(cb, null);
}
};
var user = l_accounts[account];
getUserDone(null, user.data);
//SR.User.getUser(account, getUserDone);
};
// map of token to result urls
var l_verifyURL = {};
var l_invalidURL = undefined;
var l_addToken = function (record) {
// cache to memory
l_verifyURL[record.token] = record;
// store to DB
SR.DB.setData(l_clt_name, record,
function (result) {
LOG.warn('record token to DB success: ', 'SR.Notify');
LOG.warn(result, 'SR.Notify');
},
function (result) {
LOG.warn('record token to DB fail: ', 'SR.Notify');
LOG.warn(result, 'SR.Notify');
})
}
var l_removeToken = function (token) {
delete l_verifyURL[token];
// deleteData(clt_name, onSuccess, onFail, id_or_obj)
SR.DB.deleteData(l_clt_name,
function (result) {
LOG.warn('delete token from DB success: ', 'SR.Notify');
//LOG.warn(result, 'SR.Notify');
},
function (result) {
LOG.warn('delete token from DB fail: ', 'SR.Notify');
LOG.warn(result, 'SR.Notify');
},
{token: token})
}
// get a verify URL that can re-direct to success / fail webpages
// onDone is called when the URL is clicked, returns true if verified, false if not verified
// TOFIX: if server gets shutdown / reboot, onDone will not be valid any more
exports.getVerifyURL = function (redirect_url, options, onDone) {
LOG.warn(SR.Settings.SERVER_INFO, 'SR.Notify');
var token = UTIL.createToken();
var record = {
token: token,
successURL: redirect_url.successURL,
failURL: redirect_url.failURL,
invalidURL: redirect_url.invalidURL,
onDone: onDone,
accessed: false,
// record current time
time: new Date()
};
if (options) {
for (name in options)
record[name] = options[name];
}
// TODO: save global just once
l_invalidURL = redirect_url.invalidURL;
// add a new token
l_addToken(record);
// TODO: do not hard-wired server name in code
var url = 'http://src.scalra.com:8080/' + SR.Settings.SERVER_INFO.owner + '/' + SR.Settings.SERVER_INFO.project + '/' + SR.Settings.SERVER_INFO.name + '/' +
'event/SR_VERIFY_TOKEN?token=' + token;
LOG.warn(url, 'SR.Notify');
return url;
}
//
// init DB
//
var l_clt_name = '__token';
SR.DB.useCollections([l_clt_name]);
SR.Callback.onStart(function () {
// getArray(clt_name, onSuccess, onFail, query, condition)
// load all tokens from DB (?)
SR.DB.getArray(l_clt_name,
function (result) {
LOG.sys('token record size: ' + result.length, 'SR.Notify');
for (var i=0; i < result.length; i++) {
var token = result[i].token;
l_verifyURL[token] = result[i];
// TODO: we now just pick any valid one. should make this store just once
if (result[i].invalidURL)
l_invalidURL = result[i].invalidURL;
}
})
});
SR.Callback.onStop(function () {
});
//
// handlers
//
// a pool for all message handlers
var l_handlers = exports.handlers = {};
var l_checkers = exports.checkers = {};
//-----------------------------------------
// define handlers (format checkers and event handlers)
//
//-----------------------------------------
// set the upper limit of the message queue's size
l_checkers.SR_VERIFY_TOKEN = {
token: 'string'
};
l_handlers.SR_VERIFY_TOKEN = function (event) {
var token = event.data.token;
if (l_verifyURL.hasOwnProperty(token)) {
LOG.warn('token valid: ' + token, 'SR.Notify');
// check if first time accessed
var tokenInfo = l_verifyURL[token];
var result = undefined;
if (tokenInfo.accessed === false) {
tokenInfo.accessed = true;
result = {result: true, url: tokenInfo.successURL};
// update to DB
// updateData(clt_name, query, data, onSuccess, onFail)
SR.DB.updateData(l_clt_name, {token: token}, {accessed: true},
function (result) {
LOG.sys('update token info to DB success: ', 'SR.Notify');
LOG.sys(result, 'SR.Notify');
},
function (result) {
LOG.warn('update token info to DB fail: ', 'SR.Notify');
LOG.warn(result, 'SR.Notify');
})
}
else {
LOG.warn('token already accessed before', 'SR.Notify');
result = {result: false, url: tokenInfo.failURL};
}
event.done('SR_REDIRECT', result);
UTIL.safeCall(tokenInfo.onDone, result);
}
else {
LOG.warn('token invalid: ' + token, 'SR.Notify');
if (l_invalidURL)
event.done('SR_REDIRECT', {result: false, url: l_invalidURL});
else
event.done('SR_VERIFY_TOKEN', {result: 'invalid token'});
}
}
exports.daemon = function (arg) {
switch (arg.action) {
case 'startSetInterval':
setInterval(l_setInterval, 0.1*60*1000);
break;
default:
break;
}
}
// periodically clean up timeout tokens (right now set to every 10 minutes)
var l_setInterval = function () {
// go over each token
for (var token in l_verifyURL) {
var record = l_verifyURL[token];
if (typeof record.timeout === 'undefined' || record.timeout === 0)
continue;
var now = new Date();
var elapsed = (now - record.time) / 1000 / 60;
LOG.sys('token: ' + record.token + ' eplased (in minutes): ' + elapsed, 'SR.Notify');
if (elapsed > record.timeout) {
LOG.warn('timeout! remove verify token: ' + token + ' elapsed (minutes): ' + elapsed, 'SR.Notify');
l_removeToken(token);
}
}
};
// add handlers
SR.Handler.add(exports);