Browse Source

Merge pull request #627 from isocolsky/feat/new-pushnotifications

Refactor push notifications
feat/estimateFee-limit
Matias Alejo Garcia 8 years ago
committed by GitHub
parent
commit
ae532625d4
  1. 3
      config.js
  2. 18
      lib/expressapp.js
  3. 1
      lib/model/index.js
  4. 32
      lib/model/pushnotificationsub.js
  5. 72
      lib/pushnotificationsservice.js
  6. 43
      lib/server.js
  7. 38
      lib/storage.js
  8. 2
      lib/templates/en/new_copayer.plain
  9. 2
      lib/templates/en/new_incoming_tx.plain
  10. 2
      lib/templates/en/new_outgoing_tx.plain
  11. 2
      lib/templates/en/new_tx_proposal.plain
  12. 2
      lib/templates/en/txp_finally_rejected.plain
  13. 2
      lib/templates/en/wallet_complete.plain
  14. 2
      lib/templates/es/new_copayer.plain
  15. 2
      lib/templates/es/new_outgoing_tx.plain
  16. 2
      lib/templates/es/new_tx_proposal.plain
  17. 2
      lib/templates/es/txp_finally_rejected.plain
  18. 4
      lib/templates/es/wallet_complete.plain
  19. 2
      lib/templates/fr/new_copayer.plain
  20. 2
      lib/templates/fr/new_incoming_tx.plain
  21. 2
      lib/templates/fr/new_outgoing_tx.plain
  22. 2
      lib/templates/fr/new_tx_proposal.plain
  23. 2
      lib/templates/fr/txp_finally_rejected.plain
  24. 2
      lib/templates/fr/wallet_complete.plain
  25. 11
      test/integration/emailnotifications.js
  26. 127
      test/integration/pushNotifications.js
  27. 109
      test/integration/server.js

3
config.js

@ -55,7 +55,8 @@ var config = {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000', pushServerUrl: 'https://fcm.googleapis.com/fcm',
authorizationKey: '',
}, },
fiatRateServiceOpts: { fiatRateServiceOpts: {
defaultProvider: 'BitPay', defaultProvider: 'BitPay',

18
lib/expressapp.js

@ -672,9 +672,25 @@ ExpressApp.prototype.start = function(opts, cb) {
}); });
}); });
// DEPRECATED
router.delete('/v1/pushnotifications/subscriptions/', function(req, res) { 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) { getServerWithAuth(req, res, function(server) {
server.pushNotificationsUnsubscribe(function(err, response) { server.pushNotificationsUnsubscribe(opts, function(err, response) {
if (err) return returnError(err, res, req); if (err) return returnError(err, res, req);
res.json(response); res.json(response);
}); });

1
lib/model/index.js

@ -9,5 +9,6 @@ Model.Preferences = require('./preferences');
Model.Email = require('./email'); Model.Email = require('./email');
Model.TxNote = require('./txnote'); Model.TxNote = require('./txnote');
Model.Session = require('./session'); Model.Session = require('./session');
Model.PushNotificationSub = require('./pushnotificationsub');
module.exports = Model; module.exports = Model;

32
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;

72
lib/pushnotificationsservice.js

@ -60,6 +60,10 @@ PushNotificationsService.prototype.start = function(opts, cb) {
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc'; self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || ''; self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl; self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
self.authorizationKey = opts.pushNotificationsOpts.authorizationKey;
if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.'))
async.parallel([ async.parallel([
function(done) { function(done) {
@ -117,34 +121,40 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
}, },
function(contents, next) { function(contents, next) {
async.map(recipientsList, function(recipient, next) { async.map(recipientsList, function(recipient, next) {
var opts = {};
var content = contents[recipient.language]; var content = contents[recipient.language];
opts.users = [notification.walletId + '$' + recipient.copayerId];
opts.android = { self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) {
"data": { if (err) return next(err);
"title": content.plain.subject,
"message": content.plain.body, var notifications = _.map(subs, function(sub) {
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)), return {
"notId": Math.floor(Math.random() * 100000) + 1 to: sub.token,
} priority: 'high',
}; restricted_package_name: sub.packageName,
opts.ios = { notification: {
"alert": { title: content.plain.subject,
"title": content.plain.subject, body: content.plain.body,
"body": content.plain.body sound: "default",
}, click_action: "FCM_PLUGIN_ACTIVITY",
"sound": "default", icon: "fcm_push_icon",
"payload": { },
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)) data: {
} 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); },
}, next); };
});
return next(err, notifications);
});
}, function(err, allNotifications) {
if (err) return next(err);
return next(null, _.flatten(allNotifications));
});
}, },
function(optsList, next) { function(notifications, next) {
async.each(optsList, async.each(notifications,
function(opts, next) { function(notification, next) {
self._makeRequest(opts, function(err, response) { self._makeRequest(notification, function(err, response) {
if (err) log.error(err); if (err) log.error(err);
if (response) { if (response) {
log.debug('Request status: ', response.statusCode); log.debug('Request status: ', response.statusCode);
@ -360,10 +370,12 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) {
url: self.pushServerUrl + '/send', url: self.pushServerUrl + '/send',
method: 'POST', method: 'POST',
json: true, json: true,
body: opts headers: {
}, function(err, response) { 'Content-Type': 'application/json',
return cb(err, response); 'Authorization': 'key=' + self.authorizationKey,
}); },
body: opts,
}, cb);
}; };
module.exports = PushNotificationsService; module.exports = PushNotificationsService;

43
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) { WalletService.prototype.pushNotificationsSubscribe = function(opts, cb) {
if (!checkRequired(opts, ['token'], cb)) return; if (!checkRequired(opts, ['token'], cb)) return;
var self = this; var self = this;
opts.user = self.walletId + '$' + self.copayerId; var sub = Model.PushNotificationSub.create({
copayerId: self.copayerId,
request({ token: opts.token,
url: config.pushNotificationsOpts.pushServerUrl + '/subscribe', packageName: opts.packageName,
method: 'POST', platform: opts.platform,
json: true,
body: opts
}, function(err, response) {
return cb(err, response);
}); });
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; var self = this;
request({ self.storage.removePushNotificationSub(self.copayerId, opts.token, cb);
url: config.pushNotificationsOpts.pushServerUrl + '/unsubscribe',
method: 'POST',
json: true,
body: {
user: self.walletId + '$' + self.copayerId
}
}, function(err, response) {
return cb(err, response);
});
}; };
module.exports = WalletService; module.exports = WalletService;

38
lib/storage.js

@ -24,6 +24,7 @@ var collections = {
FIAT_RATES: 'fiat_rates', FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes', TX_NOTES: 'tx_notes',
SESSIONS: 'sessions', SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
}; };
var Storage = function(opts) { var Storage = function(opts) {
@ -74,6 +75,9 @@ Storage.prototype._createIndexes = function() {
walletId: 1, walletId: 1,
txid: 1, txid: 1,
}); });
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({
copayerId: 1,
});
}; };
Storage.prototype.connect = function(opts, cb) { Storage.prototype.connect = function(opts, cb) {
@ -890,6 +894,40 @@ Storage.prototype.storeSession = function(session, cb) {
}, 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) { Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log; fn = fn || console.log;
cb = cb || function() {}; cb = cb || function() {};

2
lib/templates/en/new_copayer.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}New copayer {{subjectPrefix}}New copayer
A new copayer just joined your wallet {{walletName}}. A new copayer just joined your wallet.

2
lib/templates/en/new_incoming_tx.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}New payment received {{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.

2
lib/templates/en/new_outgoing_tx.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Payment sent {{subjectPrefix}}Payment sent
A Payment of {{amount}} has been sent from your wallet {{walletName}}. A Payment of {{amount}} has been sent from your wallet.

2
lib/templates/en/new_tx_proposal.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}New payment proposal {{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.

2
lib/templates/en/txp_finally_rejected.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Payment proposal rejected {{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.

2
lib/templates/en/wallet_complete.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Wallet complete {{subjectPrefix}}Wallet complete
Your wallet {{walletName}} is complete. Your wallet is complete.

2
lib/templates/es/new_copayer.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Nuevo copayer {{subjectPrefix}}Nuevo copayer
Un nuevo copayer ha ingresado a su monedero {{walletName}}. Un nuevo copayer ha ingresado a su billetera.

2
lib/templates/es/new_outgoing_tx.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Pago enviado {{subjectPrefix}}Pago enviado
Un pago de {{amount}} ha sido enviado de su monedero {{walletName}}. Un pago de {{amount}} ha sido enviado de su billetera.

2
lib/templates/es/new_tx_proposal.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Nueva propuesta de pago {{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.

2
lib/templates/es/txp_finally_rejected.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Propuesta de pago rechazada {{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.

4
lib/templates/es/wallet_complete.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Monedero completo {{subjectPrefix}}Billetera completa
Su monedero {{walletName}} está completo. Su billetera está completa.

2
lib/templates/fr/new_copayer.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouveau copayer {{subjectPrefix}}Nouveau copayer
Un nouveau copayer vient de rejoindre votre portefeuille {{walletName}}. Un nouveau copayer vient de rejoindre votre portefeuille.

2
lib/templates/fr/new_incoming_tx.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouveau paiement reçu {{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.

2
lib/templates/fr/new_outgoing_tx.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Paiement envoyé {{subjectPrefix}}Paiement envoyé
Un paiement de {{amount}} a été envoyé de votre portefeuille {{walletName}}. Un paiement de {{amount}} a été envoyé de votre portefeuille.

2
lib/templates/fr/new_tx_proposal.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouvelle proposition de paiement {{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.

2
lib/templates/fr/txp_finally_rejected.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Proposition de paiement rejetée {{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.

2
lib/templates/fr/wallet_complete.plain

@ -1,2 +1,2 @@
{{subjectPrefix}}Portefeuille terminé {{subjectPrefix}}Portefeuille terminé
Votre portefeuille {{walletName}} est terminé. Votre portefeuille est terminé.

11
test/integration/emailnotifications.js

@ -98,11 +98,8 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment proposal'); 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); should.exist(one.html);
one.html.indexOf('<html>').should.equal(0); one.html.indexOf('<html>').should.equal(0);
one.html.should.contain(wallet.name);
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
unsent.should.be.empty; unsent.should.be.empty;
@ -202,7 +199,6 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment sent'); one.subject.should.contain('Payment sent');
one.text.should.contain(wallet.name);
one.text.should.contain('800,000'); one.text.should.contain('800,000');
should.exist(one.html); should.exist(one.html);
one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid); one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid);
@ -258,9 +254,6 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment proposal rejected'); 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) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
unsent.should.be.empty; unsent.should.be.empty;
@ -291,7 +284,6 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received'); one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000'); one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
@ -327,7 +319,6 @@ describe('Email notifications', function() {
var one = emails[0]; var one = emails[0];
one.from.should.equal('bws@dummy.net'); one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received'); one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000'); one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) { server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err); should.not.exist(err);
@ -367,14 +358,12 @@ describe('Email notifications', function() {
}); });
spanish.from.should.equal('bws@dummy.net'); spanish.from.should.equal('bws@dummy.net');
spanish.subject.should.contain('Nuevo pago recibido'); spanish.subject.should.contain('Nuevo pago recibido');
spanish.text.should.contain(wallet.name);
spanish.text.should.contain('0.123 BTC'); spanish.text.should.contain('0.123 BTC');
var english = _.find(emails, { var english = _.find(emails, {
to: 'copayer2@domain.com' to: 'copayer2@domain.com'
}); });
english.from.should.equal('bws@dummy.net'); english.from.should.equal('bws@dummy.net');
english.subject.should.contain('New payment received'); english.subject.should.contain('New payment received');
english.text.should.contain(wallet.name);
english.text.should.contain('123,000 bits'); english.text.should.contain('123,000 bits');
done(); done();
}, 100); }, 100);

127
test/integration/pushNotifications.js

@ -10,6 +10,8 @@ var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
log.level = 'info'; log.level = 'info';
var sjcl = require('sjcl');
var WalletService = require('../../lib/server'); var WalletService = require('../../lib/server');
var PushNotificationsService = require('../../lib/pushnotificationsservice'); var PushNotificationsService = require('../../lib/pushnotificationsservice');
@ -36,11 +38,24 @@ describe('Push notifications', function() {
var i = 0; var i = 0;
async.eachSeries(w.copayers, function(copayer, next) { async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) { helpers.getAuthServer(copayer.id, function(server) {
server.savePreferences({ async.parallel([
email: 'copayer' + (++i) + '@domain.com',
language: 'en', function(done) {
unit: 'bit', server.savePreferences({
}, next); 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) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -59,8 +74,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
pushServerUrl: 'http://localhost:8000/send', authorizationKey: 'secret',
}, },
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -93,8 +108,8 @@ describe('Push notifications', function() {
return c.args[0]; return c.args[0];
}); });
calls.length.should.equal(1); calls.length.should.equal(1);
args[0].body.android.data.title.should.contain('New payment received'); args[0].body.notification.title.should.contain('New payment received');
args[0].body.android.data.message.should.contain('123,000'); args[0].body.notification.body.should.contain('123,000');
done(); done();
}, 100); }, 100);
}); });
@ -154,11 +169,24 @@ describe('Push notifications', function() {
var i = 0; var i = 0;
async.eachSeries(w.copayers, function(copayer, next) { async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) { helpers.getAuthServer(copayer.id, function(server) {
server.savePreferences({ async.parallel([
email: 'copayer' + (++i) + '@domain.com',
language: 'en', function(done) {
unit: 'bit', server.savePreferences({
}, next); 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) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -177,8 +205,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
pushServerUrl: 'http://localhost:8000/send', authorizationKey: 'secret',
}, },
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -214,14 +242,14 @@ describe('Push notifications', function() {
calls.length.should.equal(3); calls.length.should.equal(3);
args[0].body.android.data.title.should.contain('Nuevo pago recibido'); args[0].body.notification.title.should.contain('Nuevo pago recibido');
args[0].body.android.data.message.should.contain('0.123'); args[0].body.notification.body.should.contain('0.123');
args[1].body.android.data.title.should.contain('New payment received'); args[1].body.notification.title.should.contain('New payment received');
args[1].body.android.data.message.should.contain('123,000'); args[1].body.notification.body.should.contain('123,000');
args[2].body.android.data.title.should.contain('New payment received'); args[2].body.notification.title.should.contain('New payment received');
args[2].body.android.data.message.should.contain('123,000'); args[2].body.notification.body.should.contain('123,000');
done(); done();
}, 100); }, 100);
}); });
@ -333,9 +361,7 @@ describe('Push notifications', function() {
return c.args[0]; return c.args[0];
}); });
args[0].body.android.data.title.should.contain('Payment proposal rejected'); args[0].body.notification.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(); done();
}, 100); }, 100);
}); });
@ -392,11 +418,11 @@ describe('Push notifications', function() {
return c.args[0]; return c.args[0];
}); });
args[0].body.android.data.title.should.contain('Payment sent'); args[0].body.notification.title.should.contain('Payment sent');
args[1].body.android.data.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]); sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[0].body.data.copayerId);
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[1].body.data.copayerId);
done(); done();
}, 100); }, 100);
}); });
@ -432,7 +458,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en', defaultLanguage: 'en',
defaultUnit: 'btc', defaultUnit: 'btc',
subjectPrefix: '', subjectPrefix: '',
pushServerUrl: 'http://localhost:8000/send', pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
}, },
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
@ -452,44 +479,54 @@ describe('Push notifications', function() {
customData: 'custom data ' + (i + 1), 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) { }, function(err) {
should.not.exist(err); should.not.exist(err);
setTimeout(function() { setTimeout(function() {
var calls = requestStub.getCalls(); var calls = requestStub.getCalls();
var args = _.map(calls, function(c) { var args = _.filter(_.map(calls, function(call) {
return c.args[0]; return call.args[0];
}), function(arg) {
return arg.body.notification.title == 'New copayer';
}); });
var argu = _.compact(_.map(args, function(a) { server.getWallet(null, function(err, wallet) {
if (a.body.android.data.title == 'New copayer')
return a;
}));
server.getWallet(null, function(err, w) {
/* /*
First call - copayer2 joined First call - copayer2 joined
copayer2 should notify to copayer1 copayer2 should notify to copayer1
copayer2 should NOT be notifyed copayer2 should NOT be notifyed
*/ */
w.copayers[0].id.should.contain((argu[0].body.users[0]).split('$')[1]); var hashedCopayerIds = _.map(wallet.copayers, function(copayer) {
w.copayers[1].id.should.not.contain((argu[0].body.users[0]).split('$')[1]); 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 Second call - copayer3 joined
copayer3 should notify to copayer1 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 Third call - copayer3 joined
copayer3 should notify to copayer2 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 // copayer3 should NOT notify any other copayer
w.copayers[2].id.should.not.contain((argu[1].body.users[0]).split('$')[1]); hashedCopayerIds[2].should.not.equal((args[1].body.data.copayerId));
w.copayers[2].id.should.not.contain((argu[2].body.users[0]).split('$')[1]); hashedCopayerIds[2].should.not.equal((args[2].body.data.copayerId));
done(); done();
}); });
}, 100); }, 100);

109
test/integration/server.js

@ -7040,40 +7040,105 @@ describe('Wallet service', function() {
}); });
it('should subscribe copayer to push notifications service', function(done) { it('should subscribe copayer to push notifications service', function(done) {
request.yields();
helpers.getAuthServer(wallet.copayers[0].id, function(server) { helpers.getAuthServer(wallet.copayers[0].id, function(server) {
should.exist(server); should.exist(server);
server.pushNotificationsSubscribe({ server.pushNotificationsSubscribe({
token: 'DEVICE_TOKEN' token: 'DEVICE_TOKEN',
}, function(err, response) { packageName: 'com.wallet',
platform: 'Android',
}, function(err) {
should.not.exist(err); should.not.exist(err);
var calls = request.getCalls(); server.storage.fetchPushNotificationSubs(wallet.copayers[0].id, function(err, subs) {
calls.length.should.equal(1); should.not.exist(err);
var args = _.map(calls, function(c) { should.exist(subs);
return c.args[0]; subs.length.should.equal(1);
}); var s = subs[0];
args[0].body.user.should.contain(wallet.copayers[0].id); s.token.should.equal('DEVICE_TOKEN');
args[0].body.user.should.contain(wallet.id); s.packageName.should.equal('com.wallet');
args[0].body.token.should.contain('DEVICE_TOKEN'); s.platform.should.equal('Android')
done(); 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) { it('should unsubscribe copayer to push notifications service', function(done) {
request.yields();
helpers.getAuthServer(wallet.copayers[0].id, function(server) { helpers.getAuthServer(wallet.copayers[0].id, function(server) {
should.exist(server); should.exist(server);
server.pushNotificationsUnsubscribe(function(err, response) { async.series([
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); function(next) {
args[0].body.user.should.contain(wallet.id); 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(); done();
}); });
}); });

Loading…
Cancel
Save