Ivan Socolsky
9 years ago
6 changed files with 869 additions and 0 deletions
@ -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'); |
||||
|
}); |
@ -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