Merge pull request #429 from gabrielbazan7/feat/pushNotifications
push notifications
This commit is contained in:
commit
86c36eba84
|
@ -45,6 +45,13 @@ var config = {
|
|||
url: 'https://test-insight.bitpay.com:443',
|
||||
},
|
||||
},
|
||||
pushNotificationsOpts: {
|
||||
templatePath: './lib/templates',
|
||||
defaultLanguage: 'en',
|
||||
defaultUnit: 'btc',
|
||||
subjectPrefix: '',
|
||||
pushServerUrl: 'http://localhost:8000/send',
|
||||
},
|
||||
// To use email notifications uncomment this:
|
||||
// emailOpts: {
|
||||
// host: 'localhost',
|
||||
|
@ -60,5 +67,6 @@ var config = {
|
|||
// testnet: 'https://test-insight.bitpay.com/tx/{{txid}}',
|
||||
// },
|
||||
//},
|
||||
|
||||
};
|
||||
module.exports = config;
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var async = require('async');
|
||||
var Mustache = require('mustache');
|
||||
var defaultRequest = require('request');
|
||||
var MessageBroker = require('./messagebroker');
|
||||
var Storage = require('./storage');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Utils = require('./common/utils');
|
||||
var Model = require('./model');
|
||||
var log = require('npmlog');
|
||||
log.debug = log.verbose;
|
||||
|
||||
var PUSHNOTIFICATIONS_TYPES = {
|
||||
'NewCopayer': {
|
||||
filename: 'new_copayer',
|
||||
},
|
||||
'WalletComplete': {
|
||||
filename: 'wallet_complete',
|
||||
},
|
||||
'NewTxProposal': {
|
||||
filename: 'new_tx_proposal',
|
||||
},
|
||||
'NewOutgoingTx': {
|
||||
filename: 'new_outgoing_tx',
|
||||
},
|
||||
'NewIncomingTx': {
|
||||
filename: 'new_incoming_tx',
|
||||
},
|
||||
'TxProposalFinallyRejected': {
|
||||
filename: 'txp_finally_rejected',
|
||||
},
|
||||
};
|
||||
|
||||
function PushNotificationsService() {};
|
||||
|
||||
PushNotificationsService.prototype.start = function(opts, cb) {
|
||||
var self = this;
|
||||
opts = opts || {};
|
||||
self.request = opts.request || defaultRequest;
|
||||
|
||||
function _readDirectories(basePath, cb) {
|
||||
fs.readdir(basePath, function(err, files) {
|
||||
if (err) return cb(err);
|
||||
async.filter(files, function(file, next) {
|
||||
fs.stat(path.join(basePath, file), function(err, stats) {
|
||||
return next(!err && stats.isDirectory());
|
||||
});
|
||||
}, function(dirs) {
|
||||
return cb(null, dirs);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
self.templatePath = path.normalize((opts.pushNotificationsOpts.templatePath || (__dirname + '/templates')) + '/');
|
||||
self.defaultLanguage = opts.pushNotificationsOpts.defaultLanguage || 'en';
|
||||
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
|
||||
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
|
||||
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
|
||||
async.parallel([
|
||||
|
||||
function(done) {
|
||||
_readDirectories(self.templatePath, function(err, res) {
|
||||
self.availableLanguages = res;
|
||||
done(err);
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
if (opts.storage) {
|
||||
self.storage = opts.storage;
|
||||
done();
|
||||
} else {
|
||||
self.storage = new Storage();
|
||||
self.storage.connect(opts.storageOpts, done);
|
||||
}
|
||||
},
|
||||
function(done) {
|
||||
self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts);
|
||||
self.messageBroker.onMessage(_.bind(self._sendPushNotifications, self));
|
||||
done();
|
||||
},
|
||||
], function(err) {
|
||||
if (err) {
|
||||
log.error(err);
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) {
|
||||
var self = this;
|
||||
cb = cb || function() {};
|
||||
|
||||
var notifType = PUSHNOTIFICATIONS_TYPES[notification.type];
|
||||
if (!notifType) return cb();
|
||||
|
||||
// console.log(notification);
|
||||
|
||||
self._checkShouldSendNotif(notification, function(err, should) {
|
||||
if (err) return cb(err);
|
||||
if (!should) return cb();
|
||||
|
||||
self._getRecipientsList(notification, function(err, recipientsList) {
|
||||
if (err) return cb(err);
|
||||
|
||||
async.waterfall([
|
||||
|
||||
function(next) {
|
||||
self._readAndApplyTemplates(notification, notifType, recipientsList, next);
|
||||
},
|
||||
function(contents, next) {
|
||||
async.map(recipientsList, function(recipient, next) {
|
||||
var opts = {};
|
||||
var content = contents[recipient.language];
|
||||
opts.users = [notification.walletId + '$' + recipient.copayerId];
|
||||
opts.android = {
|
||||
"data": {
|
||||
"title": content.plain.subject,
|
||||
"message": content.plain.body
|
||||
}
|
||||
};
|
||||
opts.ios = {
|
||||
"alert": content.plain.body,
|
||||
"sound": ""
|
||||
};
|
||||
return next(err, opts);
|
||||
}, next);
|
||||
},
|
||||
function(optsList, next) {
|
||||
async.each(optsList,
|
||||
function(opts, next) {
|
||||
self._makeRequest(opts, function(err, response) {
|
||||
if (err) log.error(err);
|
||||
log.debug('Post status : ', response);
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
return next(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
], function(err) {
|
||||
if (err) {
|
||||
log.error('An error ocurred generating notification', err);
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._checkShouldSendNotif = function(notification, cb) {
|
||||
var self = this;
|
||||
|
||||
if (notification.type != 'NewTxProposal') return cb(null, true);
|
||||
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
||||
return cb(err, wallet.m > 1);
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._getRecipientsList = function(notification, cb) {
|
||||
var self = this;
|
||||
|
||||
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
||||
if (err) return cb(err);
|
||||
|
||||
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
|
||||
|
||||
if (err) log.error(err);
|
||||
if (_.isEmpty(preferences)) preferences = [];
|
||||
|
||||
var recipientPreferences = _.compact(_.map(preferences, function(p) {
|
||||
|
||||
if (!_.contains(self.availableLanguages, p.language)) {
|
||||
if (p.language)
|
||||
log.warn('Language for notifications "' + p.language + '" not available.');
|
||||
p.language = self.defaultLanguage;
|
||||
}
|
||||
|
||||
return {
|
||||
copayerId: p.copayerId,
|
||||
language: p.language,
|
||||
unit: p.unit,
|
||||
};
|
||||
}));
|
||||
|
||||
recipientPreferences = _.indexBy(recipientPreferences, 'copayerId');
|
||||
|
||||
var recipientsList = _.reject(_.map(wallet.copayers, function(copayer) {
|
||||
var p = recipientPreferences[copayer.id] || {};
|
||||
return {
|
||||
copayerId: copayer.id,
|
||||
language: p.language || self.defaultLanguage,
|
||||
unit: p.unit || self.defaultUnit,
|
||||
}
|
||||
}), {
|
||||
copayerId: notification.creatorId
|
||||
});
|
||||
|
||||
return cb(null, recipientsList);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._readAndApplyTemplates = function(notification, notifType, recipientsList, cb) {
|
||||
var self = this;
|
||||
|
||||
async.map(recipientsList, function(recipient, next) {
|
||||
async.waterfall([
|
||||
|
||||
function(next) {
|
||||
self._getDataForTemplate(notification, recipient, next);
|
||||
},
|
||||
function(data, next) {
|
||||
async.map(['plain', 'html'], function(type, next) {
|
||||
self._loadTemplate(notifType, recipient, '.' + type, function(err, template) {
|
||||
if (err && type == 'html') return next();
|
||||
if (err) return next(err);
|
||||
|
||||
self._applyTemplate(template, data, function(err, res) {
|
||||
return next(err, [type, res]);
|
||||
});
|
||||
});
|
||||
}, function(err, res) {
|
||||
return next(err, _.zipObject(res));
|
||||
});
|
||||
},
|
||||
function(result, next) {
|
||||
next(null, result);
|
||||
},
|
||||
], function(err, res) {
|
||||
next(err, [recipient.language, res]);
|
||||
});
|
||||
}, function(err, res) {
|
||||
return cb(err, _.zipObject(res));
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._getDataForTemplate = function(notification, recipient, cb) {
|
||||
var self = this;
|
||||
var UNIT_LABELS = {
|
||||
btc: 'BTC',
|
||||
bit: 'bits'
|
||||
};
|
||||
|
||||
var data = _.cloneDeep(notification.data);
|
||||
data.subjectPrefix = _.trim(self.subjectPrefix + ' ');
|
||||
if (data.amount) {
|
||||
try {
|
||||
var unit = recipient.unit.toLowerCase();
|
||||
data.amount = Utils.formatAmount(+data.amount, unit) + ' ' + UNIT_LABELS[unit];
|
||||
} catch (ex) {
|
||||
return cb(new Error('Could not format amount', ex));
|
||||
}
|
||||
}
|
||||
|
||||
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
||||
if (err) return cb(err);
|
||||
|
||||
data.walletId = wallet.id;
|
||||
data.walletName = wallet.name;
|
||||
data.walletM = wallet.m;
|
||||
data.walletN = wallet.n;
|
||||
|
||||
var copayer = _.find(wallet.copayers, {
|
||||
id: notification.creatorId
|
||||
});
|
||||
|
||||
if (copayer) {
|
||||
data.copayerId = copayer.id;
|
||||
data.copayerName = copayer.name;
|
||||
}
|
||||
|
||||
if (notification.type == 'TxProposalFinallyRejected' && data.rejectedBy) {
|
||||
var rejectors = _.map(data.rejectedBy, function(copayerId) {
|
||||
return _.find(wallet.copayers, {
|
||||
id: copayerId
|
||||
}).name
|
||||
});
|
||||
data.rejectorsNames = rejectors.join(', ');
|
||||
}
|
||||
|
||||
return cb(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._applyTemplate = function(template, data, cb) {
|
||||
if (!data) return cb(new Error('Could not apply template to empty data'));
|
||||
|
||||
var error;
|
||||
var result = _.mapValues(template, function(t) {
|
||||
try {
|
||||
return Mustache.render(t, data);
|
||||
} catch (e) {
|
||||
log.error('Could not apply data to template', e);
|
||||
error = e;
|
||||
}
|
||||
});
|
||||
|
||||
if (error) return cb(error);
|
||||
return cb(null, result);
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._loadTemplate = function(notifType, recipient, extension, cb) {
|
||||
var self = this;
|
||||
|
||||
self._readTemplateFile(recipient.language, notifType.filename + extension, function(err, template) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, self._compileTemplate(template, extension));
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._readTemplateFile = function(language, filename, cb) {
|
||||
var self = this;
|
||||
|
||||
var fullFilename = path.join(self.templatePath, language, filename);
|
||||
fs.readFile(fullFilename, 'utf8', function(err, template) {
|
||||
if (err) {
|
||||
return cb(new Error('Could not read template file ' + fullFilename, err));
|
||||
}
|
||||
return cb(null, template);
|
||||
});
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._compileTemplate = function(template, extension) {
|
||||
var lines = template.split('\n');
|
||||
if (extension == '.html') {
|
||||
lines.unshift('');
|
||||
}
|
||||
return {
|
||||
subject: lines[0],
|
||||
body: _.rest(lines).join('\n'),
|
||||
};
|
||||
};
|
||||
|
||||
PushNotificationsService.prototype._makeRequest = function(opts, cb) {
|
||||
var self = this;
|
||||
|
||||
self.request({
|
||||
url: self.pushServerUrl,
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: opts
|
||||
}, function(err, response) {
|
||||
return cb(err, response);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = PushNotificationsService;
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var log = require('npmlog');
|
||||
log.debug = log.verbose;
|
||||
|
||||
var config = require('../config');
|
||||
var PushNotificationsService = require('../lib/pushnotificationsservice');
|
||||
|
||||
var pushNotificationsService = new PushNotificationsService();
|
||||
pushNotificationsService.start(config, function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
console.log('Push Notification Service started');
|
||||
});
|
1
start.sh
1
start.sh
|
@ -33,5 +33,6 @@ run_program locker/locker.js pids/locker.pid logs/locker.log
|
|||
run_program messagebroker/messagebroker.js pids/messagebroker.pid logs/messagebroker.log
|
||||
run_program bcmonitor/bcmonitor.js pids/bcmonitor.pid logs/bcmonitor.log
|
||||
run_program emailservice/emailservice.js pids/emailservice.pid logs/emailservice.log
|
||||
run_program pushnotificationsservice/pushnotificationsservice.js pids/pushnotificationsservice.pid logs/pushnotificationsservice.log
|
||||
run_program bws.js pids/bws.pid logs/bws.log
|
||||
|
||||
|
|
1
stop.sh
1
stop.sh
|
@ -13,6 +13,7 @@ stop_program ()
|
|||
stop_program pids/bws.pid
|
||||
stop_program pids/emailservice.pid
|
||||
stop_program pids/bcmonitor.pid
|
||||
stop_program pids/pushnotificationsservice.pid
|
||||
stop_program pids/messagebroker.pid
|
||||
stop_program pids/locker.pid
|
||||
|
||||
|
|
|
@ -0,0 +1,490 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var async = require('async');
|
||||
|
||||
var chai = require('chai');
|
||||
var sinon = require('sinon');
|
||||
var should = chai.should();
|
||||
var log = require('npmlog');
|
||||
log.debug = log.verbose;
|
||||
log.level = 'info';
|
||||
|
||||
var WalletService = require('../../lib/server');
|
||||
var PushNotificationsService = require('../../lib/pushnotificationsservice');
|
||||
|
||||
var TestData = require('../testdata');
|
||||
var helpers = require('./helpers');
|
||||
|
||||
describe('Push notifications', function() {
|
||||
var server, wallet, requestStub, pushNotificationsService, walletId;
|
||||
|
||||
before(function(done) {
|
||||
helpers.before(done);
|
||||
});
|
||||
after(function(done) {
|
||||
helpers.after(done);
|
||||
});
|
||||
|
||||
describe('Single wallet', function() {
|
||||
beforeEach(function(done) {
|
||||
helpers.beforeEach(function(res) {
|
||||
helpers.createAndJoinWallet(1, 1, function(s, w) {
|
||||
server = s;
|
||||
wallet = w;
|
||||
|
||||
var i = 0;
|
||||
async.eachSeries(w.copayers, function(copayer, next) {
|
||||
helpers.getAuthServer(copayer.id, function(server) {
|
||||
server.savePreferences({
|
||||
email: 'copayer' + (++i) + '@domain.com',
|
||||
language: 'en',
|
||||
unit: 'bit',
|
||||
}, next);
|
||||
});
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
requestStub = sinon.stub();
|
||||
requestStub.yields();
|
||||
|
||||
pushNotificationsService = new PushNotificationsService();
|
||||
pushNotificationsService.start({
|
||||
lockOpts: {},
|
||||
messageBroker: server.messageBroker,
|
||||
storage: helpers.getStorage(),
|
||||
request: requestStub,
|
||||
pushNotificationsOpts: {
|
||||
templatePath: './lib/templates',
|
||||
defaultLanguage: 'en',
|
||||
defaultUnit: 'btc',
|
||||
subjectPrefix: '',
|
||||
|
||||
pushServerUrl: 'http://localhost:8000/send',
|
||||
},
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should build each notifications using preferences of the copayers', function(done) {
|
||||
server.savePreferences({
|
||||
language: 'en',
|
||||
unit: 'bit',
|
||||
}, function(err) {
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Simulate incoming tx notification
|
||||
server._notify('NewIncomingTx', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: true
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
var args = _.map(calls, function(c) {
|
||||
return c.args[0];
|
||||
});
|
||||
calls.length.should.equal(1);
|
||||
args[0].body.android.data.title.should.contain('New payment received');
|
||||
args[0].body.android.data.message.should.contain('123,000');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not notify auto-payments to creator', function(done) {
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Simulate incoming tx notification
|
||||
server._notify('NewIncomingTx', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: false
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
calls.length.should.equal(0);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify copayers when payment is received', function(done) {
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Simulate incoming tx notification
|
||||
server._notify('NewIncomingTx', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: true
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
calls.length.should.equal(1);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shared wallet', function() {
|
||||
beforeEach(function(done) {
|
||||
helpers.beforeEach(function(res) {
|
||||
helpers.createAndJoinWallet(2, 3, function(s, w) {
|
||||
server = s;
|
||||
wallet = w;
|
||||
var i = 0;
|
||||
async.eachSeries(w.copayers, function(copayer, next) {
|
||||
helpers.getAuthServer(copayer.id, function(server) {
|
||||
server.savePreferences({
|
||||
email: 'copayer' + (++i) + '@domain.com',
|
||||
language: 'en',
|
||||
unit: 'bit',
|
||||
}, next);
|
||||
});
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
requestStub = sinon.stub();
|
||||
requestStub.yields();
|
||||
|
||||
pushNotificationsService = new PushNotificationsService();
|
||||
pushNotificationsService.start({
|
||||
lockOpts: {},
|
||||
messageBroker: server.messageBroker,
|
||||
storage: helpers.getStorage(),
|
||||
request: requestStub,
|
||||
pushNotificationsOpts: {
|
||||
templatePath: './lib/templates',
|
||||
defaultLanguage: 'en',
|
||||
defaultUnit: 'btc',
|
||||
subjectPrefix: '',
|
||||
|
||||
pushServerUrl: 'http://localhost:8000/send',
|
||||
},
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should build each notifications using preferences of the copayers', function(done) {
|
||||
server.savePreferences({
|
||||
email: 'copayer1@domain.com',
|
||||
language: 'es',
|
||||
unit: 'btc',
|
||||
}, function(err) {
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Simulate incoming tx notification
|
||||
server._notify('NewIncomingTx', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: true
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
var args = _.map(calls, function(c) {
|
||||
return c.args[0];
|
||||
});
|
||||
|
||||
calls.length.should.equal(3);
|
||||
|
||||
args[0].body.android.data.title.should.contain('Nuevo pago recibido');
|
||||
args[0].body.android.data.message.should.contain('0.123');
|
||||
|
||||
args[1].body.android.data.title.should.contain('New payment received');
|
||||
args[1].body.android.data.message.should.contain('123,000');
|
||||
|
||||
args[2].body.android.data.title.should.contain('New payment received');
|
||||
args[2].body.android.data.message.should.contain('123,000');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify copayers when payment is received', function(done) {
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Simulate incoming tx notification
|
||||
server._notify('NewIncomingTx', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: true
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
calls.length.should.equal(3);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not notify auto-payments to creator', function(done) {
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Simulate incoming tx notification
|
||||
server._notify('NewIncomingTx', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: false
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
calls.length.should.equal(2);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify copayers a new tx proposal has been created', function(done) {
|
||||
helpers.stubUtxos(server, wallet, [1, 1], function() {
|
||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, {
|
||||
message: 'some message'
|
||||
});
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
server._notify('NewTxProposal', {
|
||||
txid: '999',
|
||||
address: address,
|
||||
amount: 12300000,
|
||||
}, {
|
||||
isGlobal: false
|
||||
}, function(err) {
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
calls.length.should.equal(2);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify copayers a tx has been finally rejected', function(done) {
|
||||
helpers.stubUtxos(server, wallet, 1, function() {
|
||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, {
|
||||
message: 'some message'
|
||||
});
|
||||
|
||||
var txpId;
|
||||
async.waterfall([
|
||||
|
||||
function(next) {
|
||||
server.createTxLegacy(txOpts, next);
|
||||
},
|
||||
function(txp, next) {
|
||||
txpId = txp.id;
|
||||
async.eachSeries(_.range(1, 3), function(i, next) {
|
||||
var copayer = TestData.copayers[i];
|
||||
helpers.getAuthServer(copayer.id44, function(server) {
|
||||
server.rejectTx({
|
||||
txProposalId: txp.id,
|
||||
}, next);
|
||||
});
|
||||
}, next);
|
||||
},
|
||||
], function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
var args = _.map(_.takeRight(calls, 2), function(c) {
|
||||
return c.args[0];
|
||||
});
|
||||
|
||||
args[0].body.android.data.title.should.contain('Payment proposal rejected');
|
||||
args[0].body.android.data.message.should.contain('copayer 2, copayer 3');
|
||||
args[0].body.android.data.message.should.not.contain('copayer 1');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify copayers a new outgoing tx has been created', function(done) {
|
||||
helpers.stubUtxos(server, wallet, 1, function() {
|
||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, {
|
||||
message: 'some message'
|
||||
});
|
||||
|
||||
var txp;
|
||||
async.waterfall([
|
||||
|
||||
function(next) {
|
||||
server.createTxLegacy(txOpts, next);
|
||||
},
|
||||
function(t, next) {
|
||||
txp = t;
|
||||
async.eachSeries(_.range(1, 3), function(i, next) {
|
||||
var copayer = TestData.copayers[i];
|
||||
helpers.getAuthServer(copayer.id44, function(s) {
|
||||
server = s;
|
||||
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
|
||||
server.signTx({
|
||||
txProposalId: txp.id,
|
||||
signatures: signatures,
|
||||
}, function(err, t) {
|
||||
txp = t;
|
||||
next();
|
||||
});
|
||||
});
|
||||
}, next);
|
||||
},
|
||||
function(next) {
|
||||
helpers.stubBroadcast();
|
||||
server.broadcastTx({
|
||||
txProposalId: txp.id,
|
||||
}, next);
|
||||
},
|
||||
], function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
var args = _.map(_.takeRight(calls, 2), function(c) {
|
||||
return c.args[0];
|
||||
});
|
||||
|
||||
args[0].body.android.data.title.should.contain('Payment sent');
|
||||
args[1].body.android.data.title.should.contain('Payment sent');
|
||||
|
||||
server.copayerId.should.not.equal((args[0].body.users[0]).split('$')[1]);
|
||||
server.copayerId.should.not.equal((args[1].body.users[0]).split('$')[1]);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('joinWallet', function() {
|
||||
beforeEach(function(done) {
|
||||
helpers.beforeEach(function(res) {
|
||||
server = new WalletService();
|
||||
var walletOpts = {
|
||||
name: 'my wallet',
|
||||
m: 1,
|
||||
n: 3,
|
||||
pubKey: TestData.keyPair.pub,
|
||||
};
|
||||
server.createWallet(walletOpts, function(err, wId) {
|
||||
should.not.exist(err);
|
||||
walletId = wId;
|
||||
should.exist(walletId);
|
||||
requestStub = sinon.stub();
|
||||
requestStub.yields();
|
||||
|
||||
pushNotificationsService = new PushNotificationsService();
|
||||
pushNotificationsService.start({
|
||||
lockOpts: {},
|
||||
messageBroker: server.messageBroker,
|
||||
storage: helpers.getStorage(),
|
||||
request: requestStub,
|
||||
pushNotificationsOpts: {
|
||||
templatePath: './lib/templates',
|
||||
defaultLanguage: 'en',
|
||||
defaultUnit: 'btc',
|
||||
subjectPrefix: '',
|
||||
pushServerUrl: 'http://localhost:8000/send',
|
||||
},
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify copayers when a new copayer just joined into your wallet except the one who joined', function(done) {
|
||||
async.eachSeries(_.range(3), function(i, next) {
|
||||
var copayerOpts = helpers.getSignedCopayerOpts({
|
||||
walletId: walletId,
|
||||
name: 'copayer ' + (i + 1),
|
||||
xPubKey: TestData.copayers[i].xPubKey_44H_0H_0H,
|
||||
requestPubKey: TestData.copayers[i].pubKey_1H_0,
|
||||
customData: 'custom data ' + (i + 1),
|
||||
});
|
||||
|
||||
server.joinWallet(copayerOpts, next);
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
setTimeout(function() {
|
||||
var calls = requestStub.getCalls();
|
||||
var args = _.map(calls, function(c) {
|
||||
return c.args[0];
|
||||
});
|
||||
|
||||
var argu = _.compact(_.map(args, function(a) {
|
||||
if (a.body.android.data.title == 'New copayer')
|
||||
return a;
|
||||
}));
|
||||
|
||||
server.getWallet(null, function(err, w) {
|
||||
/*
|
||||
First call - copayer2 joined
|
||||
copayer2 should notify to copayer1
|
||||
copayer2 should NOT be notifyed
|
||||
*/
|
||||
w.copayers[0].id.should.contain((argu[0].body.users[0]).split('$')[1]);
|
||||
w.copayers[1].id.should.not.contain((argu[0].body.users[0]).split('$')[1]);
|
||||
|
||||
/*
|
||||
Second call - copayer3 joined
|
||||
copayer3 should notify to copayer1
|
||||
*/
|
||||
w.copayers[0].id.should.contain((argu[1].body.users[0]).split('$')[1]);
|
||||
|
||||
/*
|
||||
Third call - copayer3 joined
|
||||
copayer3 should notify to copayer2
|
||||
*/
|
||||
w.copayers[1].id.should.contain((argu[2].body.users[0]).split('$')[1]);
|
||||
|
||||
// copayer3 should NOT notify any other copayer
|
||||
w.copayers[2].id.should.not.contain((argu[1].body.users[0]).split('$')[1]);
|
||||
w.copayers[2].id.should.not.contain((argu[2].body.users[0]).split('$')[1]);
|
||||
done();
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue