From bfc49c94f86ccabd72ad9520c62d857b602bff22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Mon, 4 Jan 2016 14:43:21 -0300 Subject: [PATCH 01/18] push notifications --- lib/pushnotificationsservice.js | 161 ++++++++++++++++++ .../pushnotificationsservice.js | 17 ++ start.sh | 1 + stop.sh | 1 + 4 files changed, 180 insertions(+) create mode 100644 lib/pushnotificationsservice.js create mode 100644 pushnotificationsservice/pushnotificationsservice.js diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js new file mode 100644 index 0000000..61f9bc3 --- /dev/null +++ b/lib/pushnotificationsservice.js @@ -0,0 +1,161 @@ +'use strict'; + +var _ = require('lodash'); +var $ = require('preconditions').singleton(); +var async = require('async'); +var Mustache = require('mustache'); +var log = require('npmlog'); +log.debug = log.verbose; +var fs = require('fs'); +var path = require('path'); +var nodemailer = require('nodemailer'); +var request = require('request'); + +var Utils = require('./common/utils'); +var Storage = require('./storage'); +var MessageBroker = require('./messagebroker'); +var Lock = require('./lock'); +var BlockchainExplorer = require('./blockchainexplorer'); + +var Model = require('./model'); + +var PUSHNOTIFICATION_TYPES = { + 'NewCopayer': { + filename: 'new_copayer', + notifyDoer: false, + }, + 'WalletComplete': { + filename: 'wallet_complete', + notifyDoer: true, + }, + 'NewTxProposal': { + filename: 'new_tx_proposal', + notifyDoer: false, + }, + 'NewOutgoingTx': { + filename: 'new_outgoing_tx', + notifyDoer: true, + }, + 'NewIncomingTx': { + filename: 'new_incoming_tx', + notifyDoer: true, + }, + 'TxProposalFinallyRejected': { + filename: 'txp_finally_rejected', + notifyDoer: false, + }, +}; + +function PushNotificationService() {}; + +PushNotificationService.prototype.start = function(opts, cb) { + var self = this; + async.parallel([ + function(done) { + self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts); + self.messageBroker.onMessage(_.bind(self.sendPushNotification, self)); + done(); + }, + ], function(err) { + if (err) { + log.error(err); + } + return cb(err); + }); +}; + +PushNotificationService.prototype.sendPushNotification = function(notification, cb) { + console.log(notification); + if (PUSHNOTIFICATION_TYPES[notification.type] == notification.type) { + + if (notification.type == 'NewIncomingTx') { + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "collapseKey": "optional", + "data": { + "title": "New incoming transaction", + "message": notification.data.amount + " bits" + } + }; + } + if (notification.type == 'NewOutgoingTx') { + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "collapseKey": "optional", + "data": { + "title": "New outgoing transaction", + "message": notification.data.amount + " bits" + } + }; + } + if (notification.type == 'NewCopayer') { + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "collapseKey": "optional", + "data": { + "title": "New copayer", + "message": "Copayer: " + notification.data.copayerName + " has joined" + } + }; + } + // if (notification.type == 'WalletComplete') { + // var opts = {}; + // opts.users = [notification.walletId]; + // opts.android = { + // "collapseKey": "optional", + // "data": { + // "title": "Wallet complete", + // "message": "Wallet complete" + // } + // }; + // } + if (notification.type == ' NewTxProposal') { + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "collapseKey": "optional", + "data": { + "title": "New proposal" + "message": "New transaction proposal created" + } + }; + } + if (notification.type == 'TxProposalFinallyRejected') { + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "collapseKey": "optional", + "data": { + "title": "Rejected" + "message": "Transaction proposal finally rejected" + } + }; + } + if (notification.type == ' TxProposalAcceptedBy') { + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "collapseKey": "optional", + "data": { + "title": "Accepted" + "message": "Transaction proposal accepted" + } + }; + } + var url = 'http://192.168.1.126:8000/send'; + request({ + url: url, + method: 'POST', + json: true, + body: opts + }, function(error, response, body) { + console.log(error); + console.log(response.statusCode); + }); + } +}; + +module.exports = PushNotificationService; diff --git a/pushnotificationsservice/pushnotificationsservice.js b/pushnotificationsservice/pushnotificationsservice.js new file mode 100644 index 0000000..9555a80 --- /dev/null +++ b/pushnotificationsservice/pushnotificationsservice.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +'use strict'; + +var _ = require('lodash'); +var log = require('npmlog'); +log.debug = log.verbose; + +var config = require('../config'); +var PushNotificationService = require('../lib/pushnotificationservice'); + +var PushNotificationService = new PushNotificationService(); +PushNotificationService.start(config, function(err) { + if (err) throw err; + + console.log('Push Notification Service started'); +}); diff --git a/start.sh b/start.sh index f979313..2f94c90 100755 --- a/start.sh +++ b/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 pushnotificationservice/pushnotificationservice.js pids/pushnotificationservice.pid logs/pushnotificationservice.log run_program bws.js pids/bws.pid logs/bws.log diff --git a/stop.sh b/stop.sh index 4b80f0b..f6a83ac 100755 --- a/stop.sh +++ b/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/pushnotificationservice.pid stop_program pids/messagebroker.pid stop_program pids/locker.pid From b193685b414a9cc03a54f576c162d38fe1634910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Mon, 4 Jan 2016 14:47:57 -0300 Subject: [PATCH 02/18] refactor --- lib/pushnotificationsservice.js | 12 ++++++------ pushnotificationsservice/pushnotificationsservice.js | 6 +++--- start.sh | 2 +- stop.sh | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 61f9bc3..9990bc1 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -19,7 +19,7 @@ var BlockchainExplorer = require('./blockchainexplorer'); var Model = require('./model'); -var PUSHNOTIFICATION_TYPES = { +var PUSHNOTIFICATIONS_TYPES = { 'NewCopayer': { filename: 'new_copayer', notifyDoer: false, @@ -46,9 +46,9 @@ var PUSHNOTIFICATION_TYPES = { }, }; -function PushNotificationService() {}; +function PushNotificationsService() {}; -PushNotificationService.prototype.start = function(opts, cb) { +PushNotificationsService.prototype.start = function(opts, cb) { var self = this; async.parallel([ function(done) { @@ -64,9 +64,9 @@ PushNotificationService.prototype.start = function(opts, cb) { }); }; -PushNotificationService.prototype.sendPushNotification = function(notification, cb) { +PushNotificationsService.prototype.sendPushNotifications = function(notification, cb) { console.log(notification); - if (PUSHNOTIFICATION_TYPES[notification.type] == notification.type) { + if (PUSHNOTIFICATIONS_TYPES[notification.type] == notification.type) { if (notification.type == 'NewIncomingTx') { var opts = {}; @@ -158,4 +158,4 @@ PushNotificationService.prototype.sendPushNotification = function(notification, } }; -module.exports = PushNotificationService; +module.exports = PushNotificationsService; diff --git a/pushnotificationsservice/pushnotificationsservice.js b/pushnotificationsservice/pushnotificationsservice.js index 9555a80..d1eddc0 100644 --- a/pushnotificationsservice/pushnotificationsservice.js +++ b/pushnotificationsservice/pushnotificationsservice.js @@ -7,10 +7,10 @@ var log = require('npmlog'); log.debug = log.verbose; var config = require('../config'); -var PushNotificationService = require('../lib/pushnotificationservice'); +var PushNotificationService = require('../lib/pushnotificationsservice'); -var PushNotificationService = new PushNotificationService(); -PushNotificationService.start(config, function(err) { +var PushNotificationsService = new PushNotificationsService(); +PushNotificationsService.start(config, function(err) { if (err) throw err; console.log('Push Notification Service started'); diff --git a/start.sh b/start.sh index 2f94c90..bfe1e55 100755 --- a/start.sh +++ b/start.sh @@ -33,6 +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 pushnotificationservice/pushnotificationservice.js pids/pushnotificationservice.pid logs/pushnotificationservice.log +run_program pushnotificationsservice/pushnotificationsservice.js pids/pushnotificationsservice.pid logs/pushnotificationsservice.log run_program bws.js pids/bws.pid logs/bws.log diff --git a/stop.sh b/stop.sh index f6a83ac..b15de9d 100755 --- a/stop.sh +++ b/stop.sh @@ -13,7 +13,7 @@ stop_program () stop_program pids/bws.pid stop_program pids/emailservice.pid stop_program pids/bcmonitor.pid -stop_program pids/pushnotificationservice.pid +stop_program pids/pushnotificationsservice.pid stop_program pids/messagebroker.pid stop_program pids/locker.pid From b7f98bb752269bcbb2bcd3c6a9c5ebf1103d5089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Mon, 4 Jan 2016 15:56:14 -0300 Subject: [PATCH 03/18] refactor --- lib/pushnotificationsservice.js | 20 ++++++++++--------- .../pushnotificationsservice.js | 6 +++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 9990bc1..e6fd6fe 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -49,11 +49,14 @@ var PUSHNOTIFICATIONS_TYPES = { function PushNotificationsService() {}; PushNotificationsService.prototype.start = function(opts, cb) { + + opts = opts || {}; var self = this; + async.parallel([ function(done) { self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts); - self.messageBroker.onMessage(_.bind(self.sendPushNotification, self)); + self.messageBroker.onMessage(_.bind(self.sendPushNotifications, self)); done(); }, ], function(err) { @@ -66,8 +69,8 @@ PushNotificationsService.prototype.start = function(opts, cb) { PushNotificationsService.prototype.sendPushNotifications = function(notification, cb) { console.log(notification); - if (PUSHNOTIFICATIONS_TYPES[notification.type] == notification.type) { + if (PUSHNOTIFICATIONS_TYPES[notification.type]) { if (notification.type == 'NewIncomingTx') { var opts = {}; opts.users = [notification.walletId]; @@ -112,13 +115,13 @@ PushNotificationsService.prototype.sendPushNotifications = function(notification // } // }; // } - if (notification.type == ' NewTxProposal') { + if (notification.type == 'NewTxProposal') { var opts = {}; opts.users = [notification.walletId]; opts.android = { "collapseKey": "optional", "data": { - "title": "New proposal" + "title": "New proposal", "message": "New transaction proposal created" } }; @@ -129,30 +132,29 @@ PushNotificationsService.prototype.sendPushNotifications = function(notification opts.android = { "collapseKey": "optional", "data": { - "title": "Rejected" + "title": "Rejected", "message": "Transaction proposal finally rejected" } }; } - if (notification.type == ' TxProposalAcceptedBy') { + if (notification.type == 'TxProposalAcceptedBy') { var opts = {}; opts.users = [notification.walletId]; opts.android = { "collapseKey": "optional", "data": { - "title": "Accepted" + "title": "Accepted", "message": "Transaction proposal accepted" } }; } - var url = 'http://192.168.1.126:8000/send'; + var url = 'http://192.168.1.121:8000/send'; request({ url: url, method: 'POST', json: true, body: opts }, function(error, response, body) { - console.log(error); console.log(response.statusCode); }); } diff --git a/pushnotificationsservice/pushnotificationsservice.js b/pushnotificationsservice/pushnotificationsservice.js index d1eddc0..f2359e7 100644 --- a/pushnotificationsservice/pushnotificationsservice.js +++ b/pushnotificationsservice/pushnotificationsservice.js @@ -7,10 +7,10 @@ var log = require('npmlog'); log.debug = log.verbose; var config = require('../config'); -var PushNotificationService = require('../lib/pushnotificationsservice'); +var PushNotificationsService = require('../lib/pushnotificationsservice'); -var PushNotificationsService = new PushNotificationsService(); -PushNotificationsService.start(config, function(err) { +var pushNotificationsService = new PushNotificationsService(); +pushNotificationsService.start(config, function(err) { if (err) throw err; console.log('Push Notification Service started'); From 71f06170bfc7bf18ef586622f0d1e37d24aec1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Mon, 4 Jan 2016 16:38:47 -0300 Subject: [PATCH 04/18] bits in notifications --- lib/pushnotificationsservice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index e6fd6fe..b1c3d7b 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -78,7 +78,7 @@ PushNotificationsService.prototype.sendPushNotifications = function(notification "collapseKey": "optional", "data": { "title": "New incoming transaction", - "message": notification.data.amount + " bits" + "message": notification.data.amount / 100 + " bits" } }; } @@ -89,7 +89,7 @@ PushNotificationsService.prototype.sendPushNotifications = function(notification "collapseKey": "optional", "data": { "title": "New outgoing transaction", - "message": notification.data.amount + " bits" + "message": notification.data.amount / 100 + " bits" } }; } From bd109a37d52f1710d32ef6f964406702c95948dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Tue, 5 Jan 2016 10:53:05 -0300 Subject: [PATCH 05/18] refactor --- lib/pushnotificationsservice.js | 158 +++++------------- .../pushnotificationsservice.js | 1 - 2 files changed, 45 insertions(+), 114 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index b1c3d7b..f199847 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -1,48 +1,47 @@ 'use strict'; -var _ = require('lodash'); -var $ = require('preconditions').singleton(); var async = require('async'); -var Mustache = require('mustache'); var log = require('npmlog'); log.debug = log.verbose; -var fs = require('fs'); -var path = require('path'); -var nodemailer = require('nodemailer'); var request = require('request'); - -var Utils = require('./common/utils'); -var Storage = require('./storage'); var MessageBroker = require('./messagebroker'); -var Lock = require('./lock'); -var BlockchainExplorer = require('./blockchainexplorer'); - -var Model = require('./model'); var PUSHNOTIFICATIONS_TYPES = { 'NewCopayer': { - filename: 'new_copayer', - notifyDoer: false, + title: "New copayer", + message: function(notification) { + return ("Copayer: " + notification.data.copayerName + " has joined!"); + }; }, 'WalletComplete': { - filename: 'wallet_complete', - notifyDoer: true, + title: "Wallet complete", + message: function(notification) { + return ("All copayers has joined!"); + }; }, 'NewTxProposal': { - filename: 'new_tx_proposal', - notifyDoer: false, + title: "New proposal", + message: function(notification) { + return ("New transaction proposal created"); + }; }, 'NewOutgoingTx': { - filename: 'new_outgoing_tx', - notifyDoer: true, + title: "New outgoing transaction", + message: function(notification) { + return ((notification.data.amount / 100) + " bits"); + }; }, 'NewIncomingTx': { - filename: 'new_incoming_tx', - notifyDoer: true, + title: "New incoming transaction", + message: function(notification) { + return ((notification.data.amount / 100) + " bits"); + }; }, 'TxProposalFinallyRejected': { - filename: 'txp_finally_rejected', - notifyDoer: false, + title: "Rejected", + message: function(notification) { + return ("Transaction proposal finally rejected"); + }; }, }; @@ -68,96 +67,29 @@ PushNotificationsService.prototype.start = function(opts, cb) { }; PushNotificationsService.prototype.sendPushNotifications = function(notification, cb) { - console.log(notification); + log.debug(notification); - if (PUSHNOTIFICATIONS_TYPES[notification.type]) { - if (notification.type == 'NewIncomingTx') { - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "collapseKey": "optional", - "data": { - "title": "New incoming transaction", - "message": notification.data.amount / 100 + " bits" - } - }; + if (!PUSHNOTIFICATIONS_TYPES[notification.type]) return; + + var opts = {}; + opts.users = [notification.walletId]; + opts.android = { + "data": { + "title": PUSHNOTIFICATIONS_TYPES[notification.type].title, + "message": PUSHNOTIFICATIONS_TYPES[notification.type].message(notification) } - if (notification.type == 'NewOutgoingTx') { - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "collapseKey": "optional", - "data": { - "title": "New outgoing transaction", - "message": notification.data.amount / 100 + " bits" - } - }; - } - if (notification.type == 'NewCopayer') { - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "collapseKey": "optional", - "data": { - "title": "New copayer", - "message": "Copayer: " + notification.data.copayerName + " has joined" - } - }; - } - // if (notification.type == 'WalletComplete') { - // var opts = {}; - // opts.users = [notification.walletId]; - // opts.android = { - // "collapseKey": "optional", - // "data": { - // "title": "Wallet complete", - // "message": "Wallet complete" - // } - // }; - // } - if (notification.type == 'NewTxProposal') { - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "collapseKey": "optional", - "data": { - "title": "New proposal", - "message": "New transaction proposal created" - } - }; - } - if (notification.type == 'TxProposalFinallyRejected') { - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "collapseKey": "optional", - "data": { - "title": "Rejected", - "message": "Transaction proposal finally rejected" - } - }; - } - if (notification.type == 'TxProposalAcceptedBy') { - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "collapseKey": "optional", - "data": { - "title": "Accepted", - "message": "Transaction proposal accepted" - } - }; - } - var url = 'http://192.168.1.121:8000/send'; - request({ - url: url, - method: 'POST', - json: true, - body: opts - }, function(error, response, body) { - console.log(response.statusCode); - }); - } + }; + + var url = 'http://192.168.1.121:8000/send'; + request({ + url: url, + method: 'POST', + json: true, + body: opts + }, function(error, response, body) { + log.debug(response.statusCode); + }); +} }; module.exports = PushNotificationsService; diff --git a/pushnotificationsservice/pushnotificationsservice.js b/pushnotificationsservice/pushnotificationsservice.js index f2359e7..b0acaeb 100644 --- a/pushnotificationsservice/pushnotificationsservice.js +++ b/pushnotificationsservice/pushnotificationsservice.js @@ -2,7 +2,6 @@ 'use strict'; -var _ = require('lodash'); var log = require('npmlog'); log.debug = log.verbose; From 2d923e34f5be0a8ab0db986cb28cb725320f7608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Tue, 5 Jan 2016 18:26:51 -0300 Subject: [PATCH 06/18] notifications for android - without include creator --- lib/pushnotificationsservice.js | 77 ++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index f199847..fc2c443 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -1,48 +1,50 @@ 'use strict'; +var _ = require('lodash'); var async = require('async'); var log = require('npmlog'); log.debug = log.verbose; var request = require('request'); var MessageBroker = require('./messagebroker'); +var Storage = require('./storage'); var PUSHNOTIFICATIONS_TYPES = { 'NewCopayer': { title: "New copayer", message: function(notification) { return ("Copayer: " + notification.data.copayerName + " has joined!"); - }; + } }, 'WalletComplete': { title: "Wallet complete", message: function(notification) { return ("All copayers has joined!"); - }; + } }, 'NewTxProposal': { title: "New proposal", message: function(notification) { return ("New transaction proposal created"); - }; + } }, 'NewOutgoingTx': { title: "New outgoing transaction", message: function(notification) { return ((notification.data.amount / 100) + " bits"); - }; + } }, 'NewIncomingTx': { title: "New incoming transaction", message: function(notification) { return ((notification.data.amount / 100) + " bits"); - }; + } }, 'TxProposalFinallyRejected': { title: "Rejected", message: function(notification) { return ("Transaction proposal finally rejected"); - }; - }, + } + } }; function PushNotificationsService() {}; @@ -58,6 +60,10 @@ PushNotificationsService.prototype.start = function(opts, cb) { self.messageBroker.onMessage(_.bind(self.sendPushNotifications, self)); done(); }, + function(done) { + self.storage = new Storage(); + self.storage.connect(opts.storageOpts, done); + }, ], function(err) { if (err) { log.error(err); @@ -67,29 +73,48 @@ PushNotificationsService.prototype.start = function(opts, cb) { }; PushNotificationsService.prototype.sendPushNotifications = function(notification, cb) { - log.debug(notification); + var self = this; + cb = cb || function() {}; + if (!PUSHNOTIFICATIONS_TYPES[notification.type]) return cb(); - if (!PUSHNOTIFICATIONS_TYPES[notification.type]) return; + console.log(notification); - var opts = {}; - opts.users = [notification.walletId]; - opts.android = { - "data": { - "title": PUSHNOTIFICATIONS_TYPES[notification.type].title, - "message": PUSHNOTIFICATIONS_TYPES[notification.type].message(notification) - } - }; + self.storage.fetchWallet(notification.walletId, function(err, wallet) { + if (err) return cb(err); - var url = 'http://192.168.1.121:8000/send'; - request({ - url: url, - method: 'POST', - json: true, - body: opts - }, function(error, response, body) { - log.debug(response.statusCode); + var copayers = _.reject(wallet.copayers, { + id: notification.creatorId + }); + + var url = 'http://192.168.1.121:8000/send'; + var opts = {}; + + async.each(copayers, + function(c, next) { + opts.users = [notification.walletId + '$' + c.id]; + opts.android = { + "data": { + "title": PUSHNOTIFICATIONS_TYPES[notification.type].title, + "message": PUSHNOTIFICATIONS_TYPES[notification.type].message(notification) + } + }; + + request({ + url: url, + method: 'POST', + json: true, + body: opts + }, function(error, response, body) { + console.log(response.statusCode); + next(); + }); + }, + function(err) { + log.error(err); + return cb(err); + } + ); }); -} }; module.exports = PushNotificationsService; From bd503b302ca00f30346c64c834e100235e065f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Wed, 6 Jan 2016 17:34:51 -0300 Subject: [PATCH 07/18] apply templates and languages --- lib/pushnotificationsservice.js | 342 ++++++++++++++++++++++++++------ 1 file changed, 284 insertions(+), 58 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index fc2c443..19ac8b9 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -2,48 +2,35 @@ var _ = require('lodash'); var async = require('async'); -var log = require('npmlog'); -log.debug = log.verbose; +var Mustache = require('mustache'); var request = 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': { - title: "New copayer", - message: function(notification) { - return ("Copayer: " + notification.data.copayerName + " has joined!"); - } + filename: 'new_copayer', }, 'WalletComplete': { - title: "Wallet complete", - message: function(notification) { - return ("All copayers has joined!"); - } + filename: 'wallet_complete', }, 'NewTxProposal': { - title: "New proposal", - message: function(notification) { - return ("New transaction proposal created"); - } + filename: 'new_tx_proposal', }, 'NewOutgoingTx': { - title: "New outgoing transaction", - message: function(notification) { - return ((notification.data.amount / 100) + " bits"); - } + filename: 'new_outgoing_tx', }, 'NewIncomingTx': { - title: "New incoming transaction", - message: function(notification) { - return ((notification.data.amount / 100) + " bits"); - } + filename: 'new_incoming_tx', }, 'TxProposalFinallyRejected': { - title: "Rejected", - message: function(notification) { - return ("Transaction proposal finally rejected"); - } + filename: 'txp_finally_rejected', } }; @@ -52,18 +39,43 @@ function PushNotificationsService() {}; PushNotificationsService.prototype.start = function(opts, cb) { opts = opts || {}; + + 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); + }); + }); + }; + var self = this; + self.templatePath = path.normalize(((__dirname + '/templates')) + '/'); + self.defaultLanguage = 'en'; + self.defaultUnit = 'btc'; + self.subjectPrefix = ''; + self.publicTxUrlTemplate = {}; async.parallel([ function(done) { - self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts); - self.messageBroker.onMessage(_.bind(self.sendPushNotifications, self)); - done(); + _readDirectories(self.templatePath, function(err, res) { + self.availableLanguages = res; + done(err); + }); }, function(done) { 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); @@ -72,49 +84,263 @@ PushNotificationsService.prototype.start = function(opts, cb) { }); }; -PushNotificationsService.prototype.sendPushNotifications = function(notification, cb) { +PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) { var self = this; + var url = 'http://192.168.1.128:8000/send'; cb = cb || function() {}; - if (!PUSHNOTIFICATIONS_TYPES[notification.type]) return cb(); - console.log(notification); + var notifType = PUSHNOTIFICATIONS_TYPES[notification.type]; + if (!notifType) return cb(); + + self._getRecipientsList(notification.walletId, function(err, recipientsList) { + self.storage.fetchWallet(notification.walletId, function(err, wallet) { + + var resultedRecipientsList = _.reject(self._getJoinedRecipientsList(wallet, recipientsList), { + id: notification.creatorId || null + }); + + async.waterfall([ + function(next) { + self._readAndApplyTemplates(notification, notifType, resultedRecipientsList, next); + }, + function(contents, next) { + async.map(resultedRecipientsList, function(recipient, next) { + var opts = {}; + var content = contents[recipient.language]; + opts.users = [notification.walletId + '$' + recipient.id]; + opts.android = { + "data": { + "title": content.plain.subject, + "message": content.plain.body + } + }; + return next(err, opts); + }, next); + }, + function(optsList, next) { + async.each(optsList, + function(opts, next) { + request({ + url: url, + method: 'POST', + json: true, + body: opts + }, function(error, response, body) { + next(); + }); + }, + function(err) { + log.error(err); + return cb(err); + } + ); + }, + ], function(err) { + if (err) { + log.error('An error ocurred generating notification', err); + } + return cb(err); + }); + }); + }); +}; + +PushNotificationsService.prototype._getRecipientsList = function(walletId, cb) { + var self = this; + + self.storage.fetchPreferences(walletId, null, function(err, preferences) { + if (err) return cb(err); + if (_.isEmpty(preferences)) return cb(null, []); + + var recipients = _.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 { + id: p.copayerId, + language: p.language, + unit: p.unit || self.defaultUnit, + }; + })); + + return cb(null, recipients); + }); +}; + +PushNotificationsService.prototype._getJoinedRecipientsList = function(wallet, recipientsList) { + var self = this; + var _recipientsList = _.compact(_.map(wallet.copayers, function(c) { + + if (!recipientsList) return { + id: c.id, + language: 'en', + unit: self.defaultUnit + }; + + var structure = {}; + + _.forEach(recipientsList, function(r) { + if (r.id == c.id) { + structure.id = r.id; + structure.language = r.language; + structure.unit = r.unit || self.defaultUnit; + } + }); + + if (_.isEmpty(structure)) { + structure.id = c.id; + structure.language = 'en'; + structure.unit = self.defaultUnit; + } + + return structure; + })); + return _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); - var copayers = _.reject(wallet.copayers, { + data.walletId = wallet.id; + data.walletName = wallet.name; + data.walletM = wallet.m; + data.walletN = wallet.n; + + var copayer = _.find(wallet.copayers, { id: notification.creatorId }); - var url = 'http://192.168.1.121:8000/send'; - var opts = {}; + if (copayer) { + data.copayerId = copayer.id; + data.copayerName = copayer.name; + } - async.each(copayers, - function(c, next) { - opts.users = [notification.walletId + '$' + c.id]; - opts.android = { - "data": { - "title": PUSHNOTIFICATIONS_TYPES[notification.type].title, - "message": PUSHNOTIFICATIONS_TYPES[notification.type].message(notification) - } - }; + if (notification.type == 'TxProposalFinallyRejected' && data.rejectedBy) { + var rejectors = _.map(data.rejectedBy, function(copayerId) { + return _.find(wallet.copayers, { + id: copayerId + }).name + }); + data.rejectorsNames = rejectors.join(', '); + } - request({ - url: url, - method: 'POST', - json: true, - body: opts - }, function(error, response, body) { - console.log(response.statusCode); - next(); - }); - }, - function(err) { - log.error(err); - return cb(err); + if (_.contains(['NewIncomingTx', 'NewOutgoingTx'], notification.type) && data.txid) { + var urlTemplate = self.publicTxUrlTemplate[wallet.network]; + if (urlTemplate) { + try { + data.urlForTx = Mustache.render(urlTemplate, data); + } catch (ex) { + log.warn('Could not render public url for tx', ex); + } } - ); + } + 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'), + }; +}; + module.exports = PushNotificationsService; From c473182a74198c8fc73ab232d456129af66fd383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Fri, 8 Jan 2016 09:58:46 -0300 Subject: [PATCH 08/18] adding ios options --- lib/pushnotificationsservice.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 19ac8b9..11745d3 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -86,7 +86,7 @@ PushNotificationsService.prototype.start = function(opts, cb) { PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) { var self = this; - var url = 'http://192.168.1.128:8000/send'; + var url = 'http://192.168.1.143:8000/send'; cb = cb || function() {}; var notifType = PUSHNOTIFICATIONS_TYPES[notification.type]; @@ -98,7 +98,6 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio var resultedRecipientsList = _.reject(self._getJoinedRecipientsList(wallet, recipientsList), { id: notification.creatorId || null }); - async.waterfall([ function(next) { self._readAndApplyTemplates(notification, notifType, resultedRecipientsList, next); @@ -114,6 +113,11 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio "message": content.plain.body } }; + opts.ios = { + "badge": 0, + "alert": content.plain.body, + "sound": "soundName" + }; return next(err, opts); }, next); }, From aacf21778da19aa827ddb3002297b6ed6e832997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Fri, 8 Jan 2016 13:26:51 -0300 Subject: [PATCH 09/18] refactor --- lib/pushnotificationsservice.js | 56 ++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 11745d3..a497f17 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -13,6 +13,8 @@ var Model = require('./model'); var log = require('npmlog'); log.debug = log.verbose; +var self = this; + var PUSHNOTIFICATIONS_TYPES = { 'NewCopayer': { filename: 'new_copayer', @@ -34,6 +36,8 @@ var PUSHNOTIFICATIONS_TYPES = { } }; +var url = 'http://192.168.1.143:8000/send'; + function PushNotificationsService() {}; PushNotificationsService.prototype.start = function(opts, cb) { @@ -53,7 +57,6 @@ PushNotificationsService.prototype.start = function(opts, cb) { }); }; - var self = this; self.templatePath = path.normalize(((__dirname + '/templates')) + '/'); self.defaultLanguage = 'en'; self.defaultUnit = 'btc'; @@ -85,18 +88,19 @@ PushNotificationsService.prototype.start = function(opts, cb) { }; PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) { - var self = this; - var url = 'http://192.168.1.143:8000/send'; cb = cb || function() {}; var notifType = PUSHNOTIFICATIONS_TYPES[notification.type]; if (!notifType) return cb(); self._getRecipientsList(notification.walletId, function(err, recipientsList) { + if (err) log.error(err); + self.storage.fetchWallet(notification.walletId, function(err, wallet) { + if (err) log.error(err); var resultedRecipientsList = _.reject(self._getJoinedRecipientsList(wallet, recipientsList), { - id: notification.creatorId || null + id: notification.creatorId }); async.waterfall([ function(next) { @@ -114,9 +118,8 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio } }; opts.ios = { - "badge": 0, "alert": content.plain.body, - "sound": "soundName" + "sound": "" }; return next(err, opts); }, next); @@ -124,14 +127,11 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio function(optsList, next) { async.each(optsList, function(opts, next) { - request({ - url: url, - method: 'POST', - json: true, - body: opts - }, function(error, response, body) { + self._makeRequest(opts, function(err, response) { + if (err) log.error(err); + log.debug('Post status : ', response); next(); - }); + }) }, function(err) { log.error(err); @@ -150,7 +150,6 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio }; PushNotificationsService.prototype._getRecipientsList = function(walletId, cb) { - var self = this; self.storage.fetchPreferences(walletId, null, function(err, preferences) { if (err) return cb(err); @@ -159,10 +158,10 @@ PushNotificationsService.prototype._getRecipientsList = function(walletId, cb) { var recipients = _.compact(_.map(preferences, function(p) { if (!_.contains(self.availableLanguages, p.language)) { - if (!p.language) { + if (p.language) { log.warn('Language for notifications "' + p.language + '" not available.'); - p.language = self.defaultLanguage; } + p.language = self.defaultLanguage; } return { @@ -177,15 +176,8 @@ PushNotificationsService.prototype._getRecipientsList = function(walletId, cb) { }; PushNotificationsService.prototype._getJoinedRecipientsList = function(wallet, recipientsList) { - var self = this; var _recipientsList = _.compact(_.map(wallet.copayers, function(c) { - if (!recipientsList) return { - id: c.id, - language: 'en', - unit: self.defaultUnit - }; - var structure = {}; _.forEach(recipientsList, function(r) { @@ -198,7 +190,7 @@ PushNotificationsService.prototype._getJoinedRecipientsList = function(wallet, r if (_.isEmpty(structure)) { structure.id = c.id; - structure.language = 'en'; + structure.language = self.defaultLanguage; structure.unit = self.defaultUnit; } @@ -208,7 +200,6 @@ PushNotificationsService.prototype._getJoinedRecipientsList = function(wallet, r }; PushNotificationsService.prototype._readAndApplyTemplates = function(notification, notifType, recipientsList, cb) { - var self = this; async.map(recipientsList, function(recipient, next) { async.waterfall([ @@ -241,7 +232,6 @@ PushNotificationsService.prototype._readAndApplyTemplates = function(notificatio }; PushNotificationsService.prototype._getDataForTemplate = function(notification, recipient, cb) { - var self = this; var UNIT_LABELS = { btc: 'BTC', bit: 'bits' @@ -316,7 +306,6 @@ PushNotificationsService.prototype._applyTemplate = function(template, data, cb) }; 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); @@ -325,7 +314,6 @@ PushNotificationsService.prototype._loadTemplate = function(notifType, recipient }; 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) { @@ -347,4 +335,16 @@ PushNotificationsService.prototype._compileTemplate = function(template, extensi }; }; +PushNotificationService.prototype._makeRequest = function(opts, cb) { + request({ + url: url, + method: 'POST', + json: true, + body: opts + }, function(error, response) { + if (error) return cb(error); + return cb(null, response); + }); +}; + module.exports = PushNotificationsService; From a2f00de699ae5395640be98bd1929182221bb1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Fri, 8 Jan 2016 17:01:35 -0300 Subject: [PATCH 10/18] clean code --- config.js | 12 +++ lib/pushnotificationsservice.js | 175 ++++++++++++++++---------------- 2 files changed, 101 insertions(+), 86 deletions(-) diff --git a/config.js b/config.js index a4e82a4..c197cda 100644 --- a/config.js +++ b/config.js @@ -45,6 +45,17 @@ var config = { url: 'https://test-insight.bitpay.com:443', }, }, + pushNotificationsOpts: { + templatePath: './lib/templates', + defaultLanguage: 'en', + defaultUnit: 'btc', + subjectPrefix: '', + publicTxUrlTemplate: { + livenet: 'https://insight.bitpay.com/tx/{{txid}}', + testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', + }, + pushServerUrl: 'http://192.168.1.143:8000/send', + }, // To use email notifications uncomment this: // emailOpts: { // host: 'localhost', @@ -60,5 +71,6 @@ var config = { // testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', // }, //}, + }; module.exports = config; diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index a497f17..7237847 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -13,8 +13,6 @@ var Model = require('./model'); var log = require('npmlog'); log.debug = log.verbose; -var self = this; - var PUSHNOTIFICATIONS_TYPES = { 'NewCopayer': { filename: 'new_copayer', @@ -36,12 +34,10 @@ var PUSHNOTIFICATIONS_TYPES = { } }; -var url = 'http://192.168.1.143:8000/send'; - function PushNotificationsService() {}; PushNotificationsService.prototype.start = function(opts, cb) { - + var self = this; opts = opts || {}; function _readDirectories(basePath, cb) { @@ -57,11 +53,12 @@ PushNotificationsService.prototype.start = function(opts, cb) { }); }; - self.templatePath = path.normalize(((__dirname + '/templates')) + '/'); - self.defaultLanguage = 'en'; - self.defaultUnit = 'btc'; - self.subjectPrefix = ''; - self.publicTxUrlTemplate = {}; + self.templatePath = opts.pushNotificationsOpts.templatePath || templatePathpath.normalize(((__dirname + '/templates')) + '/'); + self.defaultLanguage = opts.pushNotificationsOpts.defaultLanguage || 'en'; + self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc'; + self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || ''; + self.publicTxUrlTemplate = opts.pushNotificationsOpts.publicTxUrlTemplate || {}; + self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; async.parallel([ function(done) { @@ -88,118 +85,118 @@ PushNotificationsService.prototype.start = function(opts, cb) { }; PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) { + var self = this; cb = cb || function() {}; var notifType = PUSHNOTIFICATIONS_TYPES[notification.type]; if (!notifType) return cb(); - self._getRecipientsList(notification.walletId, function(err, recipientsList) { - if (err) log.error(err); + self._getRecipientsList(notification, function(err, recipientsList) { + if (err) return cb(err); - self.storage.fetchWallet(notification.walletId, function(err, wallet) { - if (err) log.error(err); - - var resultedRecipientsList = _.reject(self._getJoinedRecipientsList(wallet, recipientsList), { - id: notification.creatorId - }); - async.waterfall([ - function(next) { - self._readAndApplyTemplates(notification, notifType, resultedRecipientsList, next); - }, - function(contents, next) { - async.map(resultedRecipientsList, function(recipient, next) { - var opts = {}; - var content = contents[recipient.language]; - opts.users = [notification.walletId + '$' + recipient.id]; - 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) { - log.error(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 } - ); - }, - ], function(err) { - if (err) { - log.error('An error ocurred generating notification', err); - } - return cb(err); - }); + }; + opts.ios = { + "alert": content.plain.body, + "sound": "" + }; + return next(err, opts); + }, next); + }, + function(optsList, next) { + async.each(optsList, + function(opts, next) { + self._makeRequest(opts, next()); + }, + function(err) { + log.error(err); + return cb(err); + } + ); + }, + ], function(err) { + if (err) { + log.error('An error ocurred generating notification', err); + } + return cb(err); }); }); }; -PushNotificationsService.prototype._getRecipientsList = function(walletId, cb) { +PushNotificationsService.prototype._getRecipientsList = function(notification, cb) { + var self = this; - self.storage.fetchPreferences(walletId, null, function(err, preferences) { + self.storage.fetchWallet(notification.walletId, function(err, wallet) { if (err) return cb(err); - if (_.isEmpty(preferences)) return cb(null, []); - var recipients = _.compact(_.map(preferences, function(p) { + self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) { + if (err) log.error(err); + if (_.isEmpty(preferences)) preferences = []; - if (!_.contains(self.availableLanguages, p.language)) { - if (p.language) { - log.warn('Language for notifications "' + p.language + '" not available.'); + var recipients = _.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; } - p.language = self.defaultLanguage; - } - return { - id: p.copayerId, - language: p.language, - unit: p.unit || self.defaultUnit, - }; - })); + return { + id: p.copayerId, + language: p.language, + unit: p.unit || self.defaultUnit, + }; + })); - return cb(null, recipients); + var recipientsList = _.reject(self._join(wallet.copayers, recipients), { + id: notification.creatorId + }); + + return cb(null, recipientsList); + }); }); }; -PushNotificationsService.prototype._getJoinedRecipientsList = function(wallet, recipientsList) { - var _recipientsList = _.compact(_.map(wallet.copayers, function(c) { +PushNotificationsService.prototype._join = function(copayers, recipients) { + var self = this; + var recipientsList = _.compact(_.map(copayers, function(c) { var structure = {}; - _.forEach(recipientsList, function(r) { + _.forEach(recipients, function(r) { if (r.id == c.id) { - structure.id = r.id; + structure.copayerId = r.id; structure.language = r.language; structure.unit = r.unit || self.defaultUnit; } }); if (_.isEmpty(structure)) { - structure.id = c.id; + structure.copayerId = c.id; structure.language = self.defaultLanguage; structure.unit = self.defaultUnit; } return structure; })); - return _recipientsList; + return recipientsList; }; PushNotificationsService.prototype._readAndApplyTemplates = function(notification, notifType, recipientsList, cb) { + var self = this; async.map(recipientsList, function(recipient, next) { async.waterfall([ @@ -232,6 +229,7 @@ PushNotificationsService.prototype._readAndApplyTemplates = function(notificatio }; PushNotificationsService.prototype._getDataForTemplate = function(notification, recipient, cb) { + var self = this; var UNIT_LABELS = { btc: 'BTC', bit: 'bits' @@ -306,6 +304,7 @@ PushNotificationsService.prototype._applyTemplate = function(template, data, cb) }; 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); @@ -314,6 +313,7 @@ PushNotificationsService.prototype._loadTemplate = function(notifType, recipient }; 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) { @@ -335,15 +335,18 @@ PushNotificationsService.prototype._compileTemplate = function(template, extensi }; }; -PushNotificationService.prototype._makeRequest = function(opts, cb) { +PushNotificationsService.prototype._makeRequest = function(opts, cb) { + var self = this; + request({ - url: url, + url: self.pushServerUrl, method: 'POST', json: true, body: opts }, function(error, response) { - if (error) return cb(error); - return cb(null, response); + if (error) log.error(error); + log.debug('Post status : ', response); + return; }); }; From 153144ace4629136ccfc4d3374c64afeca7f56cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Fri, 8 Jan 2016 17:11:28 -0300 Subject: [PATCH 11/18] fix copayer creator id error --- lib/pushnotificationsservice.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 7237847..8b1c695 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -91,6 +91,8 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio var notifType = PUSHNOTIFICATIONS_TYPES[notification.type]; if (!notifType) return cb(); + console.log(notification); + self._getRecipientsList(notification, function(err, recipientsList) { if (err) return cb(err); @@ -162,7 +164,7 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c })); var recipientsList = _.reject(self._join(wallet.copayers, recipients), { - id: notification.creatorId + copayerId: notification.creatorId }); return cb(null, recipientsList); From a8f1bdd3c9b8040a78f54ffa8dbc26e97beb31e6 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 11 Jan 2016 17:58:39 -0300 Subject: [PATCH 12/18] WIP integrations test --- test/integration/pushNotifications.js | 402 ++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 test/integration/pushNotifications.js diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js new file mode 100644 index 0000000..49d80de --- /dev/null +++ b/test/integration/pushNotifications.js @@ -0,0 +1,402 @@ +'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; + + 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', + 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: '', + publicTxUrlTemplate: { + livenet: 'https://insight.bitpay.com/tx/{{txid}}', + testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', + }, + pushServerUrl: 'http://192.168.1.111:8000/send', + }, + }, function(err) { + should.not.exist(err); + done(); + }); + }); + }); + }); + }); + + it('should build each notifications using preferences of the copayers', 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(); + var args = _.map(calls, function(c) { + return c.args[0]; + }); + + args[0].body.android.data.title.should.contain('New payment received'); + args[0].body.android.data.message.should.contain('123,000'); + + done(); + }, 100); + }); + }); + }); + + it('number of calls should be 0', 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('number of calls should be 1', 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: '', + publicTxUrlTemplate: { + livenet: 'https://insight.bitpay.com/tx/{{txid}}', + testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', + }, + pushServerUrl: 'http://192.168.1.111:8000/send', + }, + }, function(err) { + should.not.exist(err); + done(); + }); + }); + }); + }); + }); + + it('should build each notifications using preferences of the copayers', 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(); + var args = _.map(calls, function(c) { + return c.args[0]; + }); + + args[0].body.android.data.title.should.contain('New payment received'); + args[0].body.android.data.message.should.contain('123,000'); + + done(); + }, 100); + }); + }); + }); + + it('number of calls should be 3', 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('number of calls should be 2', 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.createTxLegacy(txOpts, function(err, tx) { + // should.not.exist(err); + 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 when a new copayer just joined into your wallet ', 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 join existing wallet', function(done) { + // var copayerOpts = helpers.getSignedCopayerOpts({ + // walletId: walletId, + // name: 'me', + // xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H, + // requestPubKey: TestData.copayers[0].pubKey_1H_0, + // customData: 'dummy custom data', + // }); + // server.joinWallet(copayerOpts, function(err, result) { + // should.not.exist(err); + // setTimeout(function() { + // var calls = requestStub.getCalls(); + // var args = _.map(_.takeRight(calls, 2), function(c) { + // return c.args[0]; + // }); + // console.log(args); + + // done(); + // }, 100); + // }); + // }); + }); + + +}); From a3ed3a512898f422ebb897634864156725995cb3 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 12 Jan 2016 09:44:30 -0300 Subject: [PATCH 13/18] config and pushNotifications service modified --- config.js | 2 +- lib/pushnotificationsservice.js | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/config.js b/config.js index c197cda..f5f944f 100644 --- a/config.js +++ b/config.js @@ -54,7 +54,7 @@ var config = { livenet: 'https://insight.bitpay.com/tx/{{txid}}', testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', }, - pushServerUrl: 'http://192.168.1.143:8000/send', + pushServerUrl: 'http://192.168.1.111:8000/send', }, // To use email notifications uncomment this: // emailOpts: { diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 8b1c695..d680612 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -3,7 +3,7 @@ var _ = require('lodash'); var async = require('async'); var Mustache = require('mustache'); -var request = require('request'); +var defaultRequest = require('request'); var MessageBroker = require('./messagebroker'); var Storage = require('./storage'); var fs = require('fs'); @@ -39,6 +39,7 @@ 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) { @@ -53,14 +54,14 @@ PushNotificationsService.prototype.start = function(opts, cb) { }); }; - self.templatePath = opts.pushNotificationsOpts.templatePath || templatePathpath.normalize(((__dirname + '/templates')) + '/'); + 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.publicTxUrlTemplate = opts.pushNotificationsOpts.publicTxUrlTemplate || {}; self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; - async.parallel([ + function(done) { _readDirectories(self.templatePath, function(err, res) { self.availableLanguages = res; @@ -68,8 +69,13 @@ PushNotificationsService.prototype.start = function(opts, cb) { }); }, function(done) { - self.storage = new Storage(); - self.storage.connect(opts.storageOpts, 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); @@ -82,6 +88,7 @@ PushNotificationsService.prototype.start = function(opts, cb) { } return cb(err); }); + }; PushNotificationsService.prototype._sendPushNotifications = function(notification, cb) { @@ -91,12 +98,13 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio var notifType = PUSHNOTIFICATIONS_TYPES[notification.type]; if (!notifType) return cb(); - console.log(notification); + // console.log(notification); self._getRecipientsList(notification, function(err, recipientsList) { if (err) return cb(err); async.waterfall([ + function(next) { self._readAndApplyTemplates(notification, notifType, recipientsList, next); }, @@ -124,7 +132,8 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio self._makeRequest(opts, next()); }, function(err) { - log.error(err); + if (err) + log.error(err); return cb(err); } ); @@ -145,6 +154,7 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c if (err) return cb(err); self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) { + if (err) log.error(err); if (_.isEmpty(preferences)) preferences = []; @@ -202,6 +212,7 @@ PushNotificationsService.prototype._readAndApplyTemplates = function(notificatio async.map(recipientsList, function(recipient, next) { async.waterfall([ + function(next) { self._getDataForTemplate(notification, recipient, next); }, @@ -340,7 +351,7 @@ PushNotificationsService.prototype._compileTemplate = function(template, extensi PushNotificationsService.prototype._makeRequest = function(opts, cb) { var self = this; - request({ + self.request({ url: self.pushServerUrl, method: 'POST', json: true, From 92aac31b4ca56232125fb168425ec1f6f1ceb009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Tue, 12 Jan 2016 09:53:44 -0300 Subject: [PATCH 14/18] template path fix --- test/integration/pushNotifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index 49d80de..693b231 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -54,7 +54,7 @@ describe('Push notifications', function() { storage: helpers.getStorage(), request: requestStub, pushNotificationsOpts: { - templatePath: '../lib/templates', + templatePath: './lib/templates', defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', @@ -174,7 +174,7 @@ describe('Push notifications', function() { storage: helpers.getStorage(), request: requestStub, pushNotificationsOpts: { - templatePath: '../lib/templates', + templatePath: './lib/templates', defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', From 925e83b74ab7450707d4a1bcea0259fc0e6b3d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Tue, 12 Jan 2016 10:09:06 -0300 Subject: [PATCH 15/18] pushnotificationsservice path fix --- test/integration/pushNotifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index 693b231..768fca2 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -11,7 +11,7 @@ log.debug = log.verbose; log.level = 'info'; var WalletService = require('../../lib/server'); -var PushNotificationsService = require('../../lib/pushNotificationsService'); +var PushNotificationsService = require('../../lib/pushnotificationsservice'); var TestData = require('../testdata'); var helpers = require('./helpers'); From f6aef2de7c0dfc5edf3d5e5809972754c04a7dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Tue, 12 Jan 2016 15:19:29 -0300 Subject: [PATCH 16/18] add join test --- test/integration/pushNotifications.js | 149 +++++++++++++++----------- 1 file changed, 88 insertions(+), 61 deletions(-) diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index 768fca2..4b8701d 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -17,7 +17,7 @@ var TestData = require('../testdata'); var helpers = require('./helpers'); describe('Push notifications', function() { - var server, wallet, requestStub, pushNotificationsService; + var server, wallet, requestStub, pushNotificationsService, walletId; before(function(done) { helpers.before(done); @@ -151,7 +151,6 @@ describe('Push notifications', function() { 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) { @@ -269,8 +268,6 @@ describe('Push notifications', function() { var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { message: 'some message' }); - // server.createTxLegacy(txOpts, function(err, tx) { - // should.not.exist(err); server.createAddress({}, function(err, address) { should.not.exist(err); server._notify('NewTxProposal', { @@ -289,7 +286,6 @@ describe('Push notifications', function() { }); }); }); - // }); }); it('should notify copayers a tx has been finally rejected', function(done) { @@ -332,71 +328,102 @@ describe('Push notifications', function() { }); }); }); + }); - it('should notify copayers when a new copayer just joined into your wallet ', 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) { + 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(); - 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'); + pushNotificationsService = new PushNotificationsService(); + pushNotificationsService.start({ + lockOpts: {}, + messageBroker: server.messageBroker, + storage: helpers.getStorage(), + request: requestStub, + pushNotificationsOpts: { + templatePath: './lib/templates', + defaultLanguage: 'en', + defaultUnit: 'btc', + subjectPrefix: '', + publicTxUrlTemplate: { + livenet: 'https://insight.bitpay.com/tx/{{txid}}', + testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', + }, + pushServerUrl: 'http://192.168.1.111:8000/send', + }, + }, function(err) { + should.not.exist(err); done(); - }, 100); + }); }); }); }); + it.only('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), + }); - // it('should join existing wallet', function(done) { - // var copayerOpts = helpers.getSignedCopayerOpts({ - // walletId: walletId, - // name: 'me', - // xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H, - // requestPubKey: TestData.copayers[0].pubKey_1H_0, - // customData: 'dummy custom data', - // }); - // server.joinWallet(copayerOpts, function(err, result) { - // should.not.exist(err); - // setTimeout(function() { - // var calls = requestStub.getCalls(); - // var args = _.map(_.takeRight(calls, 2), function(c) { - // return c.args[0]; - // }); - // console.log(args); + 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]; + }); - // done(); - // }, 100); - // }); - // }); + 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); + }); + }); }); - - }); From ba1c60397723897bb7f482a31153957ce3ce0533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Tue, 12 Jan 2016 17:26:44 -0300 Subject: [PATCH 17/18] add integrations test - should exclude outgoing notifications --- lib/pushnotificationsservice.js | 94 +++++++++------ test/integration/pushNotifications.js | 167 ++++++++++++++++++-------- 2 files changed, 174 insertions(+), 87 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index d680612..7b4ff48 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -31,7 +31,7 @@ var PUSHNOTIFICATIONS_TYPES = { }, 'TxProposalFinallyRejected': { filename: 'txp_finally_rejected', - } + }, }; function PushNotificationsService() {}; @@ -100,53 +100,67 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio // console.log(notification); - self._getRecipientsList(notification, function(err, recipientsList) { + self._checkShouldSendNotif(notification, function(err, should) { if (err) return cb(err); + if (!should) return cb(); - async.waterfall([ + self._getRecipientsList(notification, function(err, recipientsList) { + if (err) return cb(err); - 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 + 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, next()); + }, + function(err) { + if (err) + log.error(err); + return cb(err); } - }; - opts.ios = { - "alert": content.plain.body, - "sound": "" - }; - return next(err, opts); - }, next); - }, - function(optsList, next) { - async.each(optsList, - function(opts, next) { - self._makeRequest(opts, next()); - }, - function(err) { - if (err) - log.error(err); - return cb(err); - } - ); - }, - ], function(err) { - if (err) { - log.error('An error ocurred generating notification', err); - } - return cb(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; diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index 4b8701d..55249b1 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -38,6 +38,7 @@ describe('Push notifications', function() { helpers.getAuthServer(copayer.id, function(server) { server.savePreferences({ email: 'copayer' + (++i) + '@domain.com', + language: 'en', unit: 'bit', }, next); }); @@ -74,33 +75,37 @@ describe('Push notifications', function() { }); it('should build each notifications using preferences of the copayers', function(done) { - server.createAddress({}, function(err, address) { - should.not.exist(err); + 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]; - }); - - args[0].body.android.data.title.should.contain('New payment received'); - args[0].body.android.data.message.should.contain('123,000'); - - done(); - }, 100); + // 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('number of calls should be 0', function(done) { + it('should not notify auto-payments to creator', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); @@ -115,14 +120,13 @@ describe('Push notifications', function() { setTimeout(function() { var calls = requestStub.getCalls(); calls.length.should.equal(0); - done(); }, 100); }); }); }); - it('number of calls should be 1', function(done) { + it('should notify copayers when payment is received', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); @@ -137,7 +141,6 @@ describe('Push notifications', function() { setTimeout(function() { var calls = requestStub.getCalls(); calls.length.should.equal(1); - done(); }, 100); }); @@ -193,33 +196,46 @@ describe('Push notifications', function() { }); it('should build each notifications using preferences of the copayers', function(done) { - server.createAddress({}, function(err, address) { - should.not.exist(err); + 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]; - }); + // 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]; + }); - args[0].body.android.data.title.should.contain('New payment received'); - args[0].body.android.data.message.should.contain('123,000'); + calls.length.should.equal(3); - done(); - }, 100); + 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('number of calls should be 3', function(done) { + it('should notify copayers when payment is received', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); @@ -241,7 +257,7 @@ describe('Push notifications', function() { }); }); - it('number of calls should be 2', function(done) { + it('should not notify auto-payments to creator', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); @@ -328,6 +344,63 @@ describe('Push notifications', function() { }); }); }); + + 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.getWallet(null, function(err, w) { + 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() { @@ -372,7 +445,7 @@ describe('Push notifications', function() { }); }); - it.only('should notify copayers when a new copayer just joined into your wallet except the one who joined', function(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, From 5c1a95d5d20d96f1ee487d7bd1b3d256f3973cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Baz=C3=A1n?= Date: Wed, 13 Jan 2016 13:14:42 -0300 Subject: [PATCH 18/18] refactor --- config.js | 6 +-- lib/pushnotificationsservice.js | 69 ++++++++------------------- test/integration/pushNotifications.js | 28 ++++------- 3 files changed, 30 insertions(+), 73 deletions(-) diff --git a/config.js b/config.js index f5f944f..c3887e4 100644 --- a/config.js +++ b/config.js @@ -50,11 +50,7 @@ var config = { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - publicTxUrlTemplate: { - livenet: 'https://insight.bitpay.com/tx/{{txid}}', - testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', - }, - pushServerUrl: 'http://192.168.1.111:8000/send', + pushServerUrl: 'http://localhost:8000/send', }, // To use email notifications uncomment this: // emailOpts: { diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 7b4ff48..8d8b81c 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -58,7 +58,6 @@ PushNotificationsService.prototype.start = function(opts, cb) { self.defaultLanguage = opts.pushNotificationsOpts.defaultLanguage || 'en'; self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc'; self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || ''; - self.publicTxUrlTemplate = opts.pushNotificationsOpts.publicTxUrlTemplate || {}; self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; async.parallel([ @@ -133,12 +132,14 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio function(optsList, next) { async.each(optsList, function(opts, next) { - self._makeRequest(opts, next()); + self._makeRequest(opts, function(err, response) { + if (err) log.error(err); + log.debug('Post status : ', response); + next(); + }); }, function(err) { - if (err) - log.error(err); - return cb(err); + return next(err); } ); }, @@ -172,7 +173,7 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c if (err) log.error(err); if (_.isEmpty(preferences)) preferences = []; - var recipients = _.compact(_.map(preferences, function(p) { + var recipientPreferences = _.compact(_.map(preferences, function(p) { if (!_.contains(self.availableLanguages, p.language)) { if (p.language) @@ -181,13 +182,22 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c } return { - id: p.copayerId, + copayerId: p.copayerId, language: p.language, - unit: p.unit || self.defaultUnit, + unit: p.unit, }; })); - var recipientsList = _.reject(self._join(wallet.copayers, recipients), { + 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 }); @@ -196,31 +206,6 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c }); }; -PushNotificationsService.prototype._join = function(copayers, recipients) { - var self = this; - var recipientsList = _.compact(_.map(copayers, function(c) { - - var structure = {}; - - _.forEach(recipients, function(r) { - if (r.id == c.id) { - structure.copayerId = r.id; - structure.language = r.language; - structure.unit = r.unit || self.defaultUnit; - } - }); - - if (_.isEmpty(structure)) { - structure.copayerId = c.id; - structure.language = self.defaultLanguage; - structure.unit = self.defaultUnit; - } - - return structure; - })); - return recipientsList; -}; - PushNotificationsService.prototype._readAndApplyTemplates = function(notification, notifType, recipientsList, cb) { var self = this; @@ -299,16 +284,6 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification, data.rejectorsNames = rejectors.join(', '); } - if (_.contains(['NewIncomingTx', 'NewOutgoingTx'], notification.type) && data.txid) { - var urlTemplate = self.publicTxUrlTemplate[wallet.network]; - if (urlTemplate) { - try { - data.urlForTx = Mustache.render(urlTemplate, data); - } catch (ex) { - log.warn('Could not render public url for tx', ex); - } - } - } return cb(null, data); }); }; @@ -370,10 +345,8 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) { method: 'POST', json: true, body: opts - }, function(error, response) { - if (error) log.error(error); - log.debug('Post status : ', response); - return; + }, function(err, response) { + return cb(err, response); }); }; diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index 55249b1..c71d837 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -59,11 +59,8 @@ describe('Push notifications', function() { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - publicTxUrlTemplate: { - livenet: 'https://insight.bitpay.com/tx/{{txid}}', - testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', - }, - pushServerUrl: 'http://192.168.1.111:8000/send', + + pushServerUrl: 'http://localhost:8000/send', }, }, function(err) { should.not.exist(err); @@ -180,11 +177,8 @@ describe('Push notifications', function() { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - publicTxUrlTemplate: { - livenet: 'https://insight.bitpay.com/tx/{{txid}}', - testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', - }, - pushServerUrl: 'http://192.168.1.111:8000/send', + + pushServerUrl: 'http://localhost:8000/send', }, }, function(err) { should.not.exist(err); @@ -392,11 +386,9 @@ describe('Push notifications', function() { args[0].body.android.data.title.should.contain('Payment sent'); args[1].body.android.data.title.should.contain('Payment sent'); - server.getWallet(null, function(err, w) { - 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(); - }); + 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); }); }); @@ -431,11 +423,7 @@ describe('Push notifications', function() { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - publicTxUrlTemplate: { - livenet: 'https://insight.bitpay.com/tx/{{txid}}', - testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', - }, - pushServerUrl: 'http://192.168.1.111:8000/send', + pushServerUrl: 'http://localhost:8000/send', }, }, function(err) { should.not.exist(err);