Browse Source

Merge pull request #386 from isocolsky/ref/notifications

Ref/notifications
activeAddress staging
Matias Alejo Garcia 9 years ago
parent
commit
a0f99f6660
  1. 7
      lib/blockchainmonitor.js
  2. 3
      lib/expressapp.js
  3. 29
      lib/server.js
  4. 38
      lib/storage.js
  5. 2
      package.json
  6. 64
      test/expressapp.js
  7. 34
      test/integration/server.js

7
lib/blockchainmonitor.js

@ -83,7 +83,7 @@ BlockchainMonitor.prototype._initExplorer = function(explorer) {
log.error('Error connecting to ' + explorer.getConnectionInfo()); log.error('Error connecting to ' + explorer.getConnectionInfo());
}); });
socket.on('tx', _.bind(self._handleIncommingTx, self)); socket.on('tx', _.bind(self._handleIncommingTx, self));
socket.on('block', _.bind(self._handleNewBlock, self)); socket.on('block', _.bind(self._handleNewBlock, self, explorer.network));
}; };
BlockchainMonitor.prototype._handleTxId = function(data, processIt) { BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
@ -177,12 +177,13 @@ BlockchainMonitor.prototype._handleIncommingTx = function(data) {
this._handleTxOuts(data); this._handleTxOuts(data);
}; };
BlockchainMonitor.prototype._handleNewBlock = function(hash) { BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
var self = this; var self = this;
log.info('New block: ', hash); log.info('New ' + network + ' block: ', hash);
var notification = Notification.create({ var notification = Notification.create({
type: 'NewBlock', type: 'NewBlock',
walletId: network, // use network name as wallet id for global notifications
data: { data: {
hash: hash, hash: hash,
}, },

3
lib/expressapp.js

@ -439,8 +439,9 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/notifications/', function(req, res) { router.get('/v1/notifications/', function(req, res) {
getServerWithAuth(req, res, function(server) { getServerWithAuth(req, res, function(server) {
var timeSpan = req.query.timeSpan ? Math.min(+req.query.timeSpan || 0, 60) : 60;
var opts = { var opts = {
minTs: +Date.now() - (60 * 1000), minTs: +Date.now() - (timeSpan * 1000),
notificationId: req.query.notificationId, notificationId: req.query.notificationId,
}; };
server.getNotifications(opts, function(err, notifications) { server.getNotifications(opts, function(err, notifications) {

29
lib/server.js

@ -52,12 +52,6 @@ function WalletService() {
this.notifyTicker = 0; this.notifyTicker = 0;
}; };
WalletService.getServiceVersion = function() {
if (!serviceVersion)
serviceVersion = 'bws-' + require('../package').version;
return serviceVersion;
};
// Time after which a Tx proposal can be erased by any copayer. in seconds // Time after which a Tx proposal can be erased by any copayer. in seconds
WalletService.DELETE_LOCKTIME = 24 * 3600; WalletService.DELETE_LOCKTIME = 24 * 3600;
@ -89,6 +83,15 @@ WalletService.FEE_LEVELS = [{
}]; }];
/**
* Gets the current version of BWS
*/
WalletService.getServiceVersion = function() {
if (!serviceVersion)
serviceVersion = 'bws-' + require('../package').version;
return serviceVersion;
};
/** /**
* Initializes global settings for all instances. * Initializes global settings for all instances.
* @param {Object} opts * @param {Object} opts
@ -1657,10 +1660,22 @@ WalletService.prototype.getNotifications = function(opts, cb) {
var self = this; var self = this;
opts = opts || {}; opts = opts || {};
self.storage.fetchNotifications(self.walletId, opts.notificationId, opts.minTs || 0, function(err, notifications) { self.getWallet({}, function(err, wallet) {
if (err) return cb(err);
async.map([wallet.network, self.walletId], function(walletId, next) {
self.storage.fetchNotifications(walletId, opts.notificationId, opts.minTs || 0, next);
}, function(err, res) {
if (err) return cb(err); if (err) return cb(err);
var notifications = _.sortBy(_.map(_.flatten(res), function(n) {
n.walletId = self.walletId;
return n;
}), 'id');
return cb(null, notifications); return cb(null, notifications);
}); });
});
}; };
WalletService.prototype._normalizeTxHistory = function(txs) { WalletService.prototype._normalizeTxHistory = function(txs) {

38
lib/storage.js

@ -277,44 +277,6 @@ Storage.prototype.fetchTxs = function(walletId, opts, cb) {
}; };
/**
* fetchNotifications
*
* @param walletId
* @param opts.minTs
* @param opts.maxTs
* @param opts.limit
* @param opts.reverse
*/
Storage.prototype.fetchNotifications = function(walletId, opts, cb) {
var self = this;
opts = opts || {};
var tsFilter = {};
if (_.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs;
if (_.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs;
var filter = {
walletId: walletId
};
if (!_.isEmpty(tsFilter)) filter.createdOn = tsFilter;
var mods = {};
if (_.isNumber(opts.limit)) mods.limit = opts.limit;
this.db.collection(collections.NOTIFICATIONS).find(filter, mods).sort({
id: opts.reverse ? -1 : 1,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var notifications = _.map(result, function(notification) {
return Model.Notification.fromObj(notification);
});
return cb(null, notifications);
});
};
/** /**
* Retrieves notifications after a specific id or from a given ts (whichever is more recent). * Retrieves notifications after a specific id or from a given ts (whichever is more recent).
* *

2
package.json

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service", "name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets", "description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc", "author": "BitPay Inc",
"version": "1.0.0", "version": "1.1.0",
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
"copay", "copay",

64
test/expressapp.js

@ -84,21 +84,29 @@ describe('ExpressApp', function() {
}); });
}); });
it('/v1/notifications', function(done) { describe('/v1/notifications', function(done) {
var clock = sinon.useFakeTimers(1234000, 'Date'); var server, TestExpressApp, clock;
beforeEach(function() {
clock = sinon.useFakeTimers(1234000, 'Date');
var server = { server = {
getNotifications: sinon.stub().callsArgWith(1, null, {}) getNotifications: sinon.stub().callsArgWith(1, null, {})
}; };
var TestExpressApp = proxyquire('../lib/expressapp', { TestExpressApp = proxyquire('../lib/expressapp', {
'./server': { './server': {
initialize: sinon.stub().callsArg(1), initialize: sinon.stub().callsArg(1),
getInstanceWithAuth: sinon.stub().callsArgWith(1, null, server), getInstanceWithAuth: sinon.stub().callsArgWith(1, null, server),
} }
}); });
});
afterEach(function() {
clock.restore();
});
it('should fetch notifications from a specified id', function(done) {
start(TestExpressApp, function() { start(TestExpressApp, function() {
var requestOptions = { var requestOptions = {
url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?notificationId=123&minTs=0', url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?notificationId=123',
headers: { headers: {
'x-identity': 'identity', 'x-identity': 'identity',
'x-signature': 'signature' 'x-signature': 'signature'
@ -110,14 +118,54 @@ describe('ExpressApp', function() {
body.should.equal('{}'); body.should.equal('{}');
server.getNotifications.calledWith({ server.getNotifications.calledWith({
notificationId: '123', notificationId: '123',
minTs: 1234000 - 60000, // override minTs argument with a hardcoded 60 seconds span minTs: +Date.now() - 60000,
}).should.be.true; }).should.be.true;
clock.restore();
done(); done();
}); });
}); });
}); });
it('should allow custom minTs within limits', function(done) {
start(TestExpressApp, function() {
var requestOptions = {
url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?timeSpan=30',
headers: {
'x-identity': 'identity',
'x-signature': 'signature'
}
};
request(requestOptions, function(err, res, body) {
should.not.exist(err);
res.statusCode.should.equal(200);
server.getNotifications.calledWith({
notificationId: undefined,
minTs: +Date.now() - 30000,
}).should.be.true;
done();
});
});
});
it('should limit minTs to 60 seconds', function(done) {
start(TestExpressApp, function() {
var requestOptions = {
url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?timeSpan=90',
headers: {
'x-identity': 'identity',
'x-signature': 'signature'
}
};
request(requestOptions, function(err, res, body) {
should.not.exist(err);
res.statusCode.should.equal(200);
body.should.equal('{}');
server.getNotifications.calledWith({
notificationId: undefined,
minTs: Date.now() - 60000, // override minTs argument with a hardcoded 60 seconds span
}).should.be.true;
done();
});
});
});
});
}); });
}); });
}); });

34
test/integration/server.js

@ -3923,6 +3923,38 @@ describe('Wallet service', function() {
}); });
}); });
it('should pull new block notifications along with wallet notifications in the last 60 seconds', function(done) {
// Simulate new block notification
server.walletId = 'livenet';
server._notify('NewBlock', {
hash: 'dummy hash',
}, {
isGlobal: true
}, function(err) {
should.not.exist(err);
server.walletId = 'testnet';
server._notify('NewBlock', {
hash: 'dummy hash',
}, {
isGlobal: true
}, function(err) {
should.not.exist(err);
server.walletId = wallet.id;
server.getNotifications({
minTs: +Date.now() - (60 * 1000),
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewBlock']);
var walletIds = _.uniq(_.pluck(notifications, 'walletId'));
walletIds.length.should.equal(1);
walletIds[0].should.equal(wallet.id);
done();
});
});
});
});
it('should pull notifications in the last 60 seconds', function(done) { it('should pull notifications in the last 60 seconds', function(done) {
server.getNotifications({ server.getNotifications({
minTs: +Date.now() - (60 * 1000), minTs: +Date.now() - (60 * 1000),
@ -4134,7 +4166,7 @@ describe('Wallet service', function() {
}); });
}, },
function(next) { function(next) {
server.getNotifications({}, function(err, items) { server.storage.fetchNotifications(wallet.id, null, 0, function(err, items) {
items.length.should.equal(0); items.length.should.equal(0);
next(); next();
}); });

Loading…
Cancel
Save