From 62de18b8abe5c480a73bd2445c57a03606c85aa3 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 15:17:27 -0300 Subject: [PATCH 01/13] add push notification subscription model --- lib/model/pushnotificationsub.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 lib/model/pushnotificationsub.js diff --git a/lib/model/pushnotificationsub.js b/lib/model/pushnotificationsub.js new file mode 100644 index 0000000..c0ee377 --- /dev/null +++ b/lib/model/pushnotificationsub.js @@ -0,0 +1,32 @@ +'use strict'; + +function PushNotificationSub() {}; + +PushNotificationSub.create = function(opts) { + opts = opts || {}; + + var x = new PushNotificationSub(); + + x.version = '1.0.0'; + x.createdOn = Math.floor(Date.now() / 1000); + x.copayerId = opts.copayerId; + x.token = opts.token; + x.packageName = opts.packageName; + x.platform = opts.platform; + return x; +}; + +PushNotificationSub.fromObj = function(obj) { + var x = new PushNotificationSub(); + + x.version = obj.version; + x.createdOn = obj.createdOn; + x.copayerId = obj.copayerId; + x.token = obj.token; + x.packageName = obj.packageName; + x.platform = obj.platform; + return x; +}; + + +module.exports = PushNotificationSub; From c65704a29cb7e8d4fd19578821393da959dcd22d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 15:17:49 -0300 Subject: [PATCH 02/13] add secret key placeholder to config --- config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config.js b/config.js index 6c0bc4e..cda9ca1 100644 --- a/config.js +++ b/config.js @@ -56,6 +56,7 @@ var config = { defaultUnit: 'btc', subjectPrefix: '', pushServerUrl: 'http://localhost:8000', + authorizationKey: '', }, fiatRateServiceOpts: { defaultProvider: 'BitPay', From bfcd14d2973f3d22b31ce9192fc9a5e1273bfd83 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 15:18:16 -0300 Subject: [PATCH 03/13] add supporting methods for storing/fetching/removing subscriptions --- lib/server.js | 43 +++++++++++++++++++++++-------------------- lib/storage.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/lib/server.js b/lib/server.js index 79c7a02..72cff58 100644 --- a/lib/server.js +++ b/lib/server.js @@ -3055,36 +3055,39 @@ WalletService.prototype.getFiatRate = function(opts, cb) { }); }; +/** + * Subscribe this copayer to the Push Notifications service using the specified token. + * @param {Object} opts + * @param {string} opts.token - The token representing the app/device. + * @param {string} [opts.packageName] - The restricted_package_name option associated with this token. + * @param {string} [opts.platform] - The platform associated with this token. + */ WalletService.prototype.pushNotificationsSubscribe = function(opts, cb) { if (!checkRequired(opts, ['token'], cb)) return; var self = this; - opts.user = self.walletId + '$' + self.copayerId; - - request({ - url: config.pushNotificationsOpts.pushServerUrl + '/subscribe', - method: 'POST', - json: true, - body: opts - }, function(err, response) { - return cb(err, response); + var sub = Model.PushNotificationSub.create({ + copayerId: self.copayerId, + token: opts.token, + packageName: opts.packageName, + platform: opts.platform, }); + + self.storage.storePushNotificationSub(sub, cb); }; -WalletService.prototype.pushNotificationsUnsubscribe = function(cb) { +/** + * Unsubscribe this copayer to the Push Notifications service using the specified token. + * @param {Object} opts + * @param {string} opts.token - The token representing the app/device. + */ +WalletService.prototype.pushNotificationsUnsubscribe = function(opts, cb) { + if (!checkRequired(opts, ['token'], cb)) return; + var self = this; - request({ - url: config.pushNotificationsOpts.pushServerUrl + '/unsubscribe', - method: 'POST', - json: true, - body: { - user: self.walletId + '$' + self.copayerId - } - }, function(err, response) { - return cb(err, response); - }); + self.storage.removePushNotificationSub(self.copayerId, opts.token, cb); }; module.exports = WalletService; diff --git a/lib/storage.js b/lib/storage.js index f63f968..964e2b6 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -24,6 +24,7 @@ var collections = { FIAT_RATES: 'fiat_rates', TX_NOTES: 'tx_notes', SESSIONS: 'sessions', + PUSH_NOTIFICATION_SUBS: 'push_notification_subs', }; var Storage = function(opts) { @@ -74,6 +75,9 @@ Storage.prototype._createIndexes = function() { walletId: 1, txid: 1, }); + this.db.collection(collections.PUSH_NOTIFICATION_TOKENS).createIndex({ + copayerId: 1, + }); }; Storage.prototype.connect = function(opts, cb) { @@ -890,6 +894,40 @@ Storage.prototype.storeSession = function(session, cb) { }, cb); }; +Storage.prototype.fetchPushNotificationSubs = function(copayerId, cb) { + this.db.collection(collections.PUSH_NOTIFICATION_SUBS).find({ + copayerId: copayerId, + }).toArray(function(err, result) { + if (err) return cb(err); + + if (!result) return cb(); + + var tokens = _.map([].concat(result), function(r) { + return Model.PushNotificationSub.fromObj(r); + }); + return cb(null, tokens); + }); +}; + +Storage.prototype.storePushNotificationSub = function(pushNotificationSub, cb) { + this.db.collection(collections.PUSH_NOTIFICATION_SUBS).update({ + copayerId: pushNotificationSub.copayerId, + token: pushNotificationSub.token, + }, pushNotificationSub, { + w: 1, + upsert: true, + }, cb); +}; + +Storage.prototype.removePushNotificationSub = function(copayerId, token, cb) { + this.db.collection(collections.PUSH_NOTIFICATION_SUBS).remove({ + copayerId: copayerId, + token: token, + }, { + w: 1 + }, cb); +}; + Storage.prototype._dump = function(cb, fn) { fn = fn || console.log; cb = cb || function() {}; From 8b62a3b4d8ad59fafeb483d4fd3650d4eec64160 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 15:19:11 -0300 Subject: [PATCH 04/13] retrieve all subscription tokens for each recipient --- lib/pushnotificationsservice.js | 58 +++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 3c8990e..4286c37 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -60,6 +60,10 @@ PushNotificationsService.prototype.start = function(opts, cb) { self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc'; self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || ''; self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; + self.authorizationKey = opts.pushNotificationsOpts.authorizationKey; + + if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.')); + async.parallel([ function(done) { @@ -117,28 +121,30 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio }, 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, - "walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)), - "notId": Math.floor(Math.random() * 100000) + 1 - } - }; - opts.ios = { - "alert": { - "title": content.plain.subject, - "body": content.plain.body - }, - "sound": "default", - "payload": { - "walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)) - } - }; - return next(err, opts); + + self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) { + if (err) return next(err); + + var opts = _.map(subs, function(sub) { + return { + to: sub.token, + priority: 'high', + restricted_package_name: sub.packageName, + notification: { + title: content.plain.subject, + body: content.plain.body, + sound: "default", + click_action: "FCM_PLUGIN_ACTIVITY", + icon: "fcm_push_icon", + }, + data: { + "walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)), + }, + }; + }); + return next(err, opts); + }); }, next); }, function(optsList, next) { @@ -356,14 +362,16 @@ PushNotificationsService.prototype._compileTemplate = function(template, extensi PushNotificationsService.prototype._makeRequest = function(opts, cb) { var self = this; - self.request({ + request({ url: self.pushServerUrl + '/send', method: 'POST', json: true, + header: { + 'Content-Type': 'application/json', + 'Authorization': 'key=' + self.authorizationKey, + }, body: opts - }, function(err, response) { - return cb(err, response); - }); + }, cb); }; module.exports = PushNotificationsService; From 0341a865b37f2e4ffab4b6116eb8ecc9137ac577 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 15:36:45 -0300 Subject: [PATCH 05/13] add push notification subscription model to index --- lib/model/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/model/index.js b/lib/model/index.js index 98b30bb..e67a537 100644 --- a/lib/model/index.js +++ b/lib/model/index.js @@ -9,5 +9,6 @@ Model.Preferences = require('./preferences'); Model.Email = require('./email'); Model.TxNote = require('./txnote'); Model.Session = require('./session'); +Model.PushNotificationSub = require('./pushnotificationsub'); module.exports = Model; From 6d55be53d7549456eb3ae552a3d13f8a27c5413e Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 21:25:54 -0300 Subject: [PATCH 06/13] test sending push notifications --- lib/pushnotificationsservice.js | 26 +++--- test/integration/pushNotifications.js | 129 +++++++++++++++++--------- 2 files changed, 99 insertions(+), 56 deletions(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 4286c37..493689f 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -62,7 +62,7 @@ PushNotificationsService.prototype.start = function(opts, cb) { self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; self.authorizationKey = opts.pushNotificationsOpts.authorizationKey; - if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.')); + if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.')) async.parallel([ @@ -126,7 +126,7 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) { if (err) return next(err); - var opts = _.map(subs, function(sub) { + var notifications = _.map(subs, function(sub) { return { to: sub.token, priority: 'high', @@ -139,18 +139,22 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio icon: "fcm_push_icon", }, data: { - "walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)), + walletId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)), + copayerId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(recipient.copayerId)) }, }; }); - return next(err, opts); + return next(err, notifications); }); - }, next); + }, function(err, allNotifications) { + if (err) return next(err); + return next(null, _.flatten(allNotifications)); + }); }, - function(optsList, next) { - async.each(optsList, - function(opts, next) { - self._makeRequest(opts, function(err, response) { + function(notifications, next) { + async.each(notifications, + function(notification, next) { + self._makeRequest(notification, function(err, response) { if (err) log.error(err); if (response) { log.debug('Request status: ', response.statusCode); @@ -362,7 +366,7 @@ PushNotificationsService.prototype._compileTemplate = function(template, extensi PushNotificationsService.prototype._makeRequest = function(opts, cb) { var self = this; - request({ + self.request({ url: self.pushServerUrl + '/send', method: 'POST', json: true, @@ -370,7 +374,7 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) { 'Content-Type': 'application/json', 'Authorization': 'key=' + self.authorizationKey, }, - body: opts + body: opts, }, cb); }; diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index 8ff62d1..ec0a550 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -10,6 +10,8 @@ var log = require('npmlog'); log.debug = log.verbose; log.level = 'info'; +var sjcl = require('sjcl'); + var WalletService = require('../../lib/server'); var PushNotificationsService = require('../../lib/pushnotificationsservice'); @@ -36,11 +38,24 @@ describe('Push notifications', function() { 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); + async.parallel([ + + function(done) { + server.savePreferences({ + email: 'copayer' + (++i) + '@domain.com', + language: 'en', + unit: 'bit', + }, done); + }, + function(done) { + server.pushNotificationsSubscribe({ + token: '1234', + packageName: 'com.wallet', + platform: 'Android', + }, done); + }, + ], next); + }); }, function(err) { should.not.exist(err); @@ -59,8 +74,8 @@ describe('Push notifications', function() { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - - pushServerUrl: 'http://localhost:8000/send', + pushServerUrl: 'http://localhost:8000', + authorizationKey: 'secret', }, }, function(err) { should.not.exist(err); @@ -93,8 +108,8 @@ describe('Push notifications', function() { 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'); + args[0].body.notification.title.should.contain('New payment received'); + args[0].body.notification.body.should.contain('123,000'); done(); }, 100); }); @@ -154,11 +169,24 @@ describe('Push notifications', function() { 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); + async.parallel([ + + function(done) { + server.savePreferences({ + email: 'copayer' + (++i) + '@domain.com', + language: 'en', + unit: 'bit', + }, done); + }, + function(done) { + server.pushNotificationsSubscribe({ + token: '1234', + packageName: 'com.wallet', + platform: 'Android', + }, done); + }, + ], next); + }); }, function(err) { should.not.exist(err); @@ -177,8 +205,8 @@ describe('Push notifications', function() { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - - pushServerUrl: 'http://localhost:8000/send', + pushServerUrl: 'http://localhost:8000', + authorizationKey: 'secret', }, }, function(err) { should.not.exist(err); @@ -214,14 +242,14 @@ describe('Push notifications', function() { 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[0].body.notification.title.should.contain('Nuevo pago recibido'); + args[0].body.notification.body.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[1].body.notification.title.should.contain('New payment received'); + args[1].body.notification.body.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'); + args[2].body.notification.title.should.contain('New payment received'); + args[2].body.notification.body.should.contain('123,000'); done(); }, 100); }); @@ -333,9 +361,9 @@ describe('Push notifications', function() { 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'); + args[0].body.notification.title.should.contain('Payment proposal rejected'); + args[0].body.notification.body.should.contain('copayer 2, copayer 3'); + args[0].body.notification.body.should.not.contain('copayer 1'); done(); }, 100); }); @@ -392,11 +420,11 @@ describe('Push notifications', function() { return c.args[0]; }); - args[0].body.android.data.title.should.contain('Payment sent'); - args[1].body.android.data.title.should.contain('Payment sent'); + args[0].body.notification.title.should.contain('Payment sent'); + args[1].body.notification.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]); + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[0].body.data.copayerId); + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[1].body.data.copayerId); done(); }, 100); }); @@ -432,7 +460,8 @@ describe('Push notifications', function() { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - pushServerUrl: 'http://localhost:8000/send', + pushServerUrl: 'http://localhost:8000', + authorizationKey: 'secret', }, }, function(err) { should.not.exist(err); @@ -452,44 +481,54 @@ describe('Push notifications', function() { customData: 'custom data ' + (i + 1), }); - server.joinWallet(copayerOpts, next); + server.joinWallet(copayerOpts, function(err, res) { + if (err) return next(err); + + helpers.getAuthServer(res.copayerId, function(server) { + server.pushNotificationsSubscribe({ + token: 'token:' + copayerOpts.name, + packageName: 'com.wallet', + platform: 'Android', + }, next); + }); + }); }, function(err) { should.not.exist(err); setTimeout(function() { var calls = requestStub.getCalls(); - var args = _.map(calls, function(c) { - return c.args[0]; + var args = _.filter(_.map(calls, function(call) { + return call.args[0]; + }), function(arg) { + return arg.body.notification.title == 'New copayer'; }); - var argu = _.compact(_.map(args, function(a) { - if (a.body.android.data.title == 'New copayer') - return a; - })); - - server.getWallet(null, function(err, w) { + server.getWallet(null, function(err, wallet) { /* 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]); + var hashedCopayerIds = _.map(wallet.copayers, function(copayer) { + return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(copayer.id)); + }); + hashedCopayerIds[0].should.equal((args[0].body.data.copayerId)); + hashedCopayerIds[1].should.not.equal((args[0].body.data.copayerId)); /* Second call - copayer3 joined copayer3 should notify to copayer1 */ - w.copayers[0].id.should.contain((argu[1].body.users[0]).split('$')[1]); + hashedCopayerIds[0].should.equal((args[1].body.data.copayerId)); /* Third call - copayer3 joined copayer3 should notify to copayer2 */ - w.copayers[1].id.should.contain((argu[2].body.users[0]).split('$')[1]); + hashedCopayerIds[1].should.equal((args[2].body.data.copayerId)); // 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]); + hashedCopayerIds[2].should.not.equal((args[1].body.data.copayerId)); + hashedCopayerIds[2].should.not.equal((args[2].body.data.copayerId)); done(); }); }, 100); From c4f6290fda2b10d266e9bdfa330020278c941cf0 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 2 Feb 2017 21:38:20 -0300 Subject: [PATCH 07/13] test subscribe/unsubscribe --- test/integration/server.js | 109 +++++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/test/integration/server.js b/test/integration/server.js index 7280929..ce70008 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -7032,40 +7032,105 @@ describe('Wallet service', function() { }); it('should subscribe copayer to push notifications service', function(done) { - request.yields(); helpers.getAuthServer(wallet.copayers[0].id, function(server) { should.exist(server); server.pushNotificationsSubscribe({ - token: 'DEVICE_TOKEN' - }, function(err, response) { + token: 'DEVICE_TOKEN', + packageName: 'com.wallet', + platform: 'Android', + }, function(err) { should.not.exist(err); - var calls = request.getCalls(); - calls.length.should.equal(1); - var args = _.map(calls, function(c) { - return c.args[0]; - }); - args[0].body.user.should.contain(wallet.copayers[0].id); - args[0].body.user.should.contain(wallet.id); - args[0].body.token.should.contain('DEVICE_TOKEN'); - done(); + server.storage.fetchPushNotificationSubs(wallet.copayers[0].id, function(err, subs) { + should.not.exist(err); + should.exist(subs); + subs.length.should.equal(1); + var s = subs[0]; + s.token.should.equal('DEVICE_TOKEN'); + s.packageName.should.equal('com.wallet'); + s.platform.should.equal('Android') + done(); + }); + }); + }); + }); + it('should allow multiple subscriptions for the same copayer', function(done) { + helpers.getAuthServer(wallet.copayers[0].id, function(server) { + should.exist(server); + server.pushNotificationsSubscribe({ + token: 'DEVICE_TOKEN', + packageName: 'com.wallet', + platform: 'Android', + }, function(err) { + server.pushNotificationsSubscribe({ + token: 'DEVICE_TOKEN2', + packageName: 'com.my-other-wallet', + platform: 'iOS', + }, function(err) { + should.not.exist(err); + server.storage.fetchPushNotificationSubs(wallet.copayers[0].id, function(err, subs) { + should.not.exist(err); + should.exist(subs); + subs.length.should.equal(2); + done(); + }); + }); }); }); }); it('should unsubscribe copayer to push notifications service', function(done) { - request.yields(); helpers.getAuthServer(wallet.copayers[0].id, function(server) { should.exist(server); - server.pushNotificationsUnsubscribe(function(err, response) { - should.not.exist(err); - var calls = request.getCalls(); - calls.length.should.equal(1); - var args = _.map(calls, function(c) { - return c.args[0]; - }); + async.series([ - args[0].body.user.should.contain(wallet.copayers[0].id); - args[0].body.user.should.contain(wallet.id); + function(next) { + server.pushNotificationsSubscribe({ + token: 'DEVICE_TOKEN', + packageName: 'com.wallet', + platform: 'Android', + }, next); + }, + function(next) { + server.pushNotificationsSubscribe({ + token: 'DEVICE_TOKEN2', + packageName: 'com.my-other-wallet', + platform: 'iOS', + }, next); + }, + function(next) { + server.pushNotificationsUnsubscribe({ + token: 'DEVICE_TOKEN2' + }, next); + }, + function(next) { + server.storage.fetchPushNotificationSubs(wallet.copayers[0].id, function(err, subs) { + should.not.exist(err); + should.exist(subs); + subs.length.should.equal(1); + var s = subs[0]; + s.token.should.equal('DEVICE_TOKEN'); + next(); + }); + }, + function(next) { + helpers.getAuthServer(wallet.copayers[1].id, function(server) { + server.pushNotificationsUnsubscribe({ + token: 'DEVICE_TOKEN' + }, next); + }); + }, + function(next) { + server.storage.fetchPushNotificationSubs(wallet.copayers[0].id, function(err, subs) { + should.not.exist(err); + should.exist(subs); + subs.length.should.equal(1); + var s = subs[0]; + s.token.should.equal('DEVICE_TOKEN'); + next(); + }); + }, + ], function(err) { + should.not.exist(err); done(); }); }); From fd7e26372c17631c3919ddbe8f6ae53463f33bab Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 3 Feb 2017 11:35:16 -0300 Subject: [PATCH 08/13] fix index definition --- lib/storage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/storage.js b/lib/storage.js index 964e2b6..2cb08f2 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -75,7 +75,7 @@ Storage.prototype._createIndexes = function() { walletId: 1, txid: 1, }); - this.db.collection(collections.PUSH_NOTIFICATION_TOKENS).createIndex({ + this.db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({ copayerId: 1, }); }; From b9d0b1667404ec72162bfd65ae9a89c68ad8871e Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 3 Feb 2017 12:17:41 -0300 Subject: [PATCH 09/13] fix request headers --- lib/pushnotificationsservice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pushnotificationsservice.js b/lib/pushnotificationsservice.js index 493689f..badd855 100644 --- a/lib/pushnotificationsservice.js +++ b/lib/pushnotificationsservice.js @@ -370,7 +370,7 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) { url: self.pushServerUrl + '/send', method: 'POST', json: true, - header: { + headers: { 'Content-Type': 'application/json', 'Authorization': 'key=' + self.authorizationKey, }, From f7d7dc691a3384463e1273445c28573547a554fd Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 3 Feb 2017 14:25:25 -0300 Subject: [PATCH 10/13] default to google push notification url --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index cda9ca1..f2dff51 100644 --- a/config.js +++ b/config.js @@ -55,7 +55,7 @@ var config = { defaultLanguage: 'en', defaultUnit: 'btc', subjectPrefix: '', - pushServerUrl: 'http://localhost:8000', + pushServerUrl: 'https://fcm.googleapis.com/fcm', authorizationKey: '', }, fiatRateServiceOpts: { From b484e2754bbae5f20bc1721a583c34c2adcb31ac Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 3 Feb 2017 14:35:05 -0300 Subject: [PATCH 11/13] unsubscribe v2 --- lib/expressapp.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/expressapp.js b/lib/expressapp.js index bfc402b..cd06558 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -672,9 +672,25 @@ ExpressApp.prototype.start = function(opts, cb) { }); }); + // DEPRECATED router.delete('/v1/pushnotifications/subscriptions/', function(req, res) { + logDeprecated(req); + getServerWithAuth(req, res, function(server) { + server.pushNotificationsUnsubscribe({ + token: 'dummy' + }, function(err, response) { + if (err) return returnError(err, res, req); + res.json(response); + }); + }); + }); + + router.delete('/v2/pushnotifications/subscriptions/:token', function(req, res) { + var opts = { + token: req.params['token'], + }; getServerWithAuth(req, res, function(server) { - server.pushNotificationsUnsubscribe(function(err, response) { + server.pushNotificationsUnsubscribe(opts, function(err, response) { if (err) return returnError(err, res, req); res.json(response); }); From 19534efac012ae1d412a07d6f5f16119a5be010c Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Fri, 3 Feb 2017 14:54:10 -0300 Subject: [PATCH 12/13] Updates wording for notifications --- lib/templates/en/new_copayer.plain | 2 +- lib/templates/en/new_incoming_tx.plain | 2 +- lib/templates/en/new_outgoing_tx.plain | 2 +- lib/templates/en/new_tx_proposal.plain | 2 +- lib/templates/en/txp_finally_rejected.plain | 2 +- lib/templates/en/wallet_complete.plain | 2 +- lib/templates/es/new_copayer.plain | 2 +- lib/templates/es/new_outgoing_tx.plain | 2 +- lib/templates/es/new_tx_proposal.plain | 2 +- lib/templates/es/txp_finally_rejected.plain | 2 +- lib/templates/es/wallet_complete.plain | 4 ++-- lib/templates/fr/new_copayer.plain | 2 +- lib/templates/fr/new_incoming_tx.plain | 2 +- lib/templates/fr/new_outgoing_tx.plain | 2 +- lib/templates/fr/new_tx_proposal.plain | 2 +- lib/templates/fr/txp_finally_rejected.plain | 2 +- lib/templates/fr/wallet_complete.plain | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/templates/en/new_copayer.plain b/lib/templates/en/new_copayer.plain index 0a9cc69..2fe6a0b 100644 --- a/lib/templates/en/new_copayer.plain +++ b/lib/templates/en/new_copayer.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}New copayer -A new copayer just joined your wallet {{walletName}}. +A new copayer just joined your wallet. diff --git a/lib/templates/en/new_incoming_tx.plain b/lib/templates/en/new_incoming_tx.plain index 4acc0c1..ee4c2d3 100644 --- a/lib/templates/en/new_incoming_tx.plain +++ b/lib/templates/en/new_incoming_tx.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}New payment received -A payment of {{amount}} has been received into your wallet {{walletName}}. +A payment of {{amount}} has been received into your wallet. diff --git a/lib/templates/en/new_outgoing_tx.plain b/lib/templates/en/new_outgoing_tx.plain index c8f92cb..246e628 100644 --- a/lib/templates/en/new_outgoing_tx.plain +++ b/lib/templates/en/new_outgoing_tx.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Payment sent -A Payment of {{amount}} has been sent from your wallet {{walletName}}. +A Payment of {{amount}} has been sent from your wallet. diff --git a/lib/templates/en/new_tx_proposal.plain b/lib/templates/en/new_tx_proposal.plain index 56f2313..48bdca6 100644 --- a/lib/templates/en/new_tx_proposal.plain +++ b/lib/templates/en/new_tx_proposal.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}New payment proposal -A new payment proposal has been created in your wallet {{walletName}} by {{copayerName}}. +A new payment proposal has been created in your wallet. diff --git a/lib/templates/en/txp_finally_rejected.plain b/lib/templates/en/txp_finally_rejected.plain index 72db5e9..5d25631 100644 --- a/lib/templates/en/txp_finally_rejected.plain +++ b/lib/templates/en/txp_finally_rejected.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Payment proposal rejected -A payment proposal in your wallet {{walletName}} has been rejected by {{rejectorsNames}}. +A payment proposal in your wallet has been rejected. diff --git a/lib/templates/en/wallet_complete.plain b/lib/templates/en/wallet_complete.plain index e18902a..427bb89 100644 --- a/lib/templates/en/wallet_complete.plain +++ b/lib/templates/en/wallet_complete.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Wallet complete -Your wallet {{walletName}} is complete. +Your wallet is complete. diff --git a/lib/templates/es/new_copayer.plain b/lib/templates/es/new_copayer.plain index 81ca74c..61b3631 100644 --- a/lib/templates/es/new_copayer.plain +++ b/lib/templates/es/new_copayer.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Nuevo copayer -Un nuevo copayer ha ingresado a su monedero {{walletName}}. +Un nuevo copayer ha ingresado a su billetera. diff --git a/lib/templates/es/new_outgoing_tx.plain b/lib/templates/es/new_outgoing_tx.plain index ebcb6e2..103af4d 100644 --- a/lib/templates/es/new_outgoing_tx.plain +++ b/lib/templates/es/new_outgoing_tx.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Pago enviado -Un pago de {{amount}} ha sido enviado de su monedero {{walletName}}. +Un pago de {{amount}} ha sido enviado de su billetera. diff --git a/lib/templates/es/new_tx_proposal.plain b/lib/templates/es/new_tx_proposal.plain index 3c5cd0e..4d49ade 100644 --- a/lib/templates/es/new_tx_proposal.plain +++ b/lib/templates/es/new_tx_proposal.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Nueva propuesta de pago -Una nueva propuesta de pago ha sido creada en su monedero {{walletName}} por {{copayerName}}. +Una nueva propuesta de pago ha sido creada en su billetera. diff --git a/lib/templates/es/txp_finally_rejected.plain b/lib/templates/es/txp_finally_rejected.plain index 0eecd49..1e4b0a7 100644 --- a/lib/templates/es/txp_finally_rejected.plain +++ b/lib/templates/es/txp_finally_rejected.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Propuesta de pago rechazada -Una propuesta de pago en su monedero {{walletName}} ha sido rechazada por {{rejectorsNames}}. +Una propuesta de pago en su billetera ha sido rechazada. diff --git a/lib/templates/es/wallet_complete.plain b/lib/templates/es/wallet_complete.plain index bb2ada6..eea00c2 100644 --- a/lib/templates/es/wallet_complete.plain +++ b/lib/templates/es/wallet_complete.plain @@ -1,2 +1,2 @@ -{{subjectPrefix}}Monedero completo -Su monedero {{walletName}} está completo. +{{subjectPrefix}}Billetera completa +Su billetera está completa. diff --git a/lib/templates/fr/new_copayer.plain b/lib/templates/fr/new_copayer.plain index c95f0dc..052dbc1 100644 --- a/lib/templates/fr/new_copayer.plain +++ b/lib/templates/fr/new_copayer.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Nouveau copayer -Un nouveau copayer vient de rejoindre votre portefeuille {{walletName}}. +Un nouveau copayer vient de rejoindre votre portefeuille. diff --git a/lib/templates/fr/new_incoming_tx.plain b/lib/templates/fr/new_incoming_tx.plain index 7d3ea86..20ffa38 100644 --- a/lib/templates/fr/new_incoming_tx.plain +++ b/lib/templates/fr/new_incoming_tx.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Nouveau paiement reçu -Un paiement de {{amount}} a été reçu dans votre portefeuille {{walletName}}. +Un paiement de {{amount}} a été reçu dans votre portefeuille. diff --git a/lib/templates/fr/new_outgoing_tx.plain b/lib/templates/fr/new_outgoing_tx.plain index ed97826..52b4dde 100644 --- a/lib/templates/fr/new_outgoing_tx.plain +++ b/lib/templates/fr/new_outgoing_tx.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Paiement envoyé -Un paiement de {{amount}} a été envoyé de votre portefeuille {{walletName}}. +Un paiement de {{amount}} a été envoyé de votre portefeuille. diff --git a/lib/templates/fr/new_tx_proposal.plain b/lib/templates/fr/new_tx_proposal.plain index d0f70f2..d602135 100644 --- a/lib/templates/fr/new_tx_proposal.plain +++ b/lib/templates/fr/new_tx_proposal.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Nouvelle proposition de paiement -Une nouvelle proposition de paiement a été créée dans votre portefeuille {{walletName}} par {{copayerName}}. +Une nouvelle proposition de paiement a été créée dans votre portefeuille. diff --git a/lib/templates/fr/txp_finally_rejected.plain b/lib/templates/fr/txp_finally_rejected.plain index b25e1f7..4548dec 100644 --- a/lib/templates/fr/txp_finally_rejected.plain +++ b/lib/templates/fr/txp_finally_rejected.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Proposition de paiement rejetée -Une proposition de paiement dans votre portefeuille {{walletName}} a été rejetée par {{rejectorsNames}}. +Une proposition de paiement dans votre portefeuille a été rejetée. diff --git a/lib/templates/fr/wallet_complete.plain b/lib/templates/fr/wallet_complete.plain index 634b1f0..f38b109 100644 --- a/lib/templates/fr/wallet_complete.plain +++ b/lib/templates/fr/wallet_complete.plain @@ -1,2 +1,2 @@ {{subjectPrefix}}Portefeuille terminé -Votre portefeuille {{walletName}} est terminé. +Votre portefeuille est terminé. From 78224e7837bf5a02de35462af2d3cdbe412b23bb Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Fri, 3 Feb 2017 16:43:55 -0300 Subject: [PATCH 13/13] Fix tests for notifications --- test/integration/emailnotifications.js | 11 ----------- test/integration/pushNotifications.js | 2 -- 2 files changed, 13 deletions(-) diff --git a/test/integration/emailnotifications.js b/test/integration/emailnotifications.js index 8558814..638991c 100644 --- a/test/integration/emailnotifications.js +++ b/test/integration/emailnotifications.js @@ -98,11 +98,8 @@ describe('Email notifications', function() { var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('New payment proposal'); - one.text.should.contain(wallet.name); - one.text.should.contain(wallet.copayers[0].name); should.exist(one.html); one.html.indexOf('').should.equal(0); - one.html.should.contain(wallet.name); server.storage.fetchUnsentEmails(function(err, unsent) { should.not.exist(err); unsent.should.be.empty; @@ -202,7 +199,6 @@ describe('Email notifications', function() { var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('Payment sent'); - one.text.should.contain(wallet.name); one.text.should.contain('800,000'); should.exist(one.html); one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid); @@ -258,9 +254,6 @@ describe('Email notifications', function() { var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('Payment proposal rejected'); - one.text.should.contain(wallet.name); - one.text.should.contain('copayer 2, copayer 3'); - one.text.should.not.contain('copayer 1'); server.storage.fetchUnsentEmails(function(err, unsent) { should.not.exist(err); unsent.should.be.empty; @@ -291,7 +284,6 @@ describe('Email notifications', function() { var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('New payment received'); - one.text.should.contain(wallet.name); one.text.should.contain('123,000'); server.storage.fetchUnsentEmails(function(err, unsent) { should.not.exist(err); @@ -327,7 +319,6 @@ describe('Email notifications', function() { var one = emails[0]; one.from.should.equal('bws@dummy.net'); one.subject.should.contain('New payment received'); - one.text.should.contain(wallet.name); one.text.should.contain('123,000'); server.storage.fetchUnsentEmails(function(err, unsent) { should.not.exist(err); @@ -367,14 +358,12 @@ describe('Email notifications', function() { }); spanish.from.should.equal('bws@dummy.net'); spanish.subject.should.contain('Nuevo pago recibido'); - spanish.text.should.contain(wallet.name); spanish.text.should.contain('0.123 BTC'); var english = _.find(emails, { to: 'copayer2@domain.com' }); english.from.should.equal('bws@dummy.net'); english.subject.should.contain('New payment received'); - english.text.should.contain(wallet.name); english.text.should.contain('123,000 bits'); done(); }, 100); diff --git a/test/integration/pushNotifications.js b/test/integration/pushNotifications.js index ec0a550..364382d 100644 --- a/test/integration/pushNotifications.js +++ b/test/integration/pushNotifications.js @@ -362,8 +362,6 @@ describe('Push notifications', function() { }); args[0].body.notification.title.should.contain('Payment proposal rejected'); - args[0].body.notification.body.should.contain('copayer 2, copayer 3'); - args[0].body.notification.body.should.not.contain('copayer 1'); done(); }, 100); });