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

Refactor push notifications
This commit is contained in:
Matias Alejo Garcia 2017-03-15 15:57:34 -03:00 committed by GitHub
commit ae532625d4
27 changed files with 341 additions and 147 deletions

View File

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

View File

@ -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(function(err, response) {
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(opts, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});

View File

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

View File

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

View File

@ -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,34 +121,40 @@ 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);
}, next);
self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) {
if (err) return next(err);
var notifications = _.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)),
copayerId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(recipient.copayerId))
},
};
});
return next(err, notifications);
});
}, 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);
@ -360,10 +370,12 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) {
url: self.pushServerUrl + '/send',
method: 'POST',
json: true,
body: opts
}, function(err, response) {
return cb(err, response);
});
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=' + self.authorizationKey,
},
body: opts,
}, cb);
};
module.exports = PushNotificationsService;

View File

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

View File

@ -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_SUBS).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() {};

View File

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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

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

View File

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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

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

View File

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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

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

View File

@ -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('<html>').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);

View File

@ -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,7 @@ 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');
done();
}, 100);
});
@ -392,11 +418,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 +458,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 +479,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);

View File

@ -7040,40 +7040,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];
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();
});
});
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();
});
});
});
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();
});
});