Merge pull request #150 from isocolsky/feat/ws

Implement notifications via web sockets
This commit is contained in:
Gustavo Maximiliano Cortez 2015-03-26 15:34:24 -03:00
commit b57a6ad88e
7 changed files with 118 additions and 21 deletions

9
app.js
View File

@ -1,6 +1,7 @@
#!/usr/bin/env node
var ExpressApp = require('./lib/expressapp');
var WsApp = require('./lib/wsapp');
var basePath = process.env.BWS_BASE_PATH || '/bws/api';
var port = process.env.BWS_PORT || 3001;
@ -8,6 +9,12 @@ var port = process.env.BWS_PORT || 3001;
var app = ExpressApp.start({
basePath: basePath,
});
app.listen(port);
//app.listen(port);
var server = require('http').Server(app);
var ws = WsApp.start(server);
server.listen(port);
console.log('Bitcore Wallet Service running on port ' + port);

25
lib/eventbroadcaster.js Normal file
View File

@ -0,0 +1,25 @@
'use strict';
var log = require('npmlog');
log.debug = log.verbose;
var inherits = require('inherits');
var events = require('events');
var nodeutil = require('util');
function EventBroadcaster() {};
nodeutil.inherits(EventBroadcaster, events.EventEmitter);
EventBroadcaster.prototype.broadcast = function(eventName, serviceInstance, args) {
this.emit(eventName, serviceInstance, args);
};
var _eventBroadcasterInstance;
EventBroadcaster.singleton = function() {
if (!_eventBroadcasterInstance) {
_eventBroadcasterInstance = new EventBroadcaster();
}
return _eventBroadcasterInstance;
};
module.exports = EventBroadcaster.singleton();

View File

@ -37,6 +37,7 @@ Notification.create = function(opts) {
x.id = _.padLeft(now, 14, '0') + _.padLeft(opts.ticker || 0, 4, '0');
x.type = opts.type || 'general';
x.data = opts.data;
x.creatorId = opts.creatorId;
return x;
};
@ -48,6 +49,7 @@ Notification.fromObj = function(obj) {
x.id = obj.id;
x.type = obj.type,
x.data = obj.data;
x.creatorId = obj.creatorId;
return x;
};

View File

@ -4,9 +4,6 @@ var $ = require('preconditions').singleton();
var async = require('async');
var log = require('npmlog');
log.debug = log.verbose;
var inherits = require('inherits');
var events = require('events');
var nodeutil = require('util');
var WalletUtils = require('bitcore-wallet-utils');
var Bitcore = WalletUtils.Bitcore;
@ -18,6 +15,7 @@ var Explorers = require('bitcore-explorers');
var ClientError = require('./clienterror');
var Utils = require('./utils');
var Storage = require('./storage');
var EventBroadcaster = require('./eventbroadcaster');
var Wallet = require('./model/wallet');
var Copayer = require('./model/copayer');
@ -28,6 +26,7 @@ var Notification = require('./model/notification');
var initialized = false;
var storage, blockExplorer;
/**
* Creates an instance of the Bitcore Wallet Service.
* @constructor
@ -41,8 +40,9 @@ function WalletService() {
this.notifyTicker = 0;
};
nodeutil.inherits(WalletService, events.EventEmitter);
WalletService.onNotification = function(func) {
EventBroadcaster.on('notification', func);
};
/**
* Initializes global settings for all instances.
@ -161,6 +161,14 @@ WalletService.prototype._verifySignature = function(text, signature, pubKey) {
return WalletUtils.verifyMessage(text, signature, pubKey);
};
/**
* _emit
*
* @param {Object} args
*/
WalletService.prototype._emit = function(eventName, args) {
EventBroadcaster.broadcast(eventName, this, args);
};
/**
* _notify
@ -180,9 +188,10 @@ WalletService.prototype._notify = function(type, data) {
type: type,
data: data,
ticker: this.notifyTicker++,
creatorId: self.copayerId,
});
this.storage.storeNotification(walletId, n, function() {
self.emit(n);
self._emit('notification', n);
});
};

52
lib/wsapp.js Normal file
View File

@ -0,0 +1,52 @@
'use strict';
var $ = require('preconditions').singleton();
var _ = require('lodash');
var async = require('async');
var log = require('npmlog');
var express = require('express');
var querystring = require('querystring');
var bodyParser = require('body-parser')
var Uuid = require('uuid');
var WalletService = require('./server');
log.debug = log.verbose;
log.level = 'debug';
var subscriptions = {};
var WsApp = function() {};
WsApp._unauthorized = function() {
socket.emit('unauthorized');
socket.disconnect();
};
WsApp.start = function(server) {
var self = this;
var io = require('socket.io')(server);
WalletService.onNotification(function(serviceInstance, args) {
io.to(serviceInstance.walletId).emit('notification', args);
});
io.on('connection', function(socket) {
socket.nonce = Uuid.v4();
socket.emit('challenge', socket.nonce);
socket.on('authorize', function(data) {
if (data.message != socket.nonce) return WsApp.unauthorized();
WalletService.getInstanceWithAuth(data, function(err, res) {
if (err) return WsApp.unauthorized();
socket.join(res.walletId);
socket.emit('authorized');
});
});
});
};
module.exports = WsApp;

View File

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "0.0.13",
"version": "0.0.14",
"keywords": [
"bitcoin",
"copay",
@ -35,6 +35,7 @@
"read": "^1.0.5",
"request": "^2.53.0",
"sjcl": "^1.0.2",
"socket.io": "^1.3.5",
"uuid": "*"
},
"devDependencies": {
@ -53,13 +54,10 @@
"coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
"contributors": [{
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
},
{
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}
]
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
}, {
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}]
}

View File

@ -1611,6 +1611,7 @@ describe('Copay server', function() {
should.not.exist(err);
var last = _.last(notifications);
last.type.should.equal('TxProposalFinallyAccepted');
last.creatorId.should.equal(wallet.copayers[1].id);
last.data.txProposalId.should.equal(txp.id);
done();
});
@ -1868,6 +1869,9 @@ describe('Copay server', function() {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewTxProposal', 'NewAddress']);
var creators = _.uniq(_.pluck(notifications, 'creatorId'));
creators.length.should.equal(1);
creators[0].should.equal(wallet.copayers[0].id);
done();
});
});
@ -1945,7 +1949,7 @@ describe('Copay server', function() {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[2];
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
sinon.spy(server, 'emit');
sinon.spy(server, '_emit');
server.signTx({
txProposalId: tx.id,
signatures: signatures,
@ -1964,9 +1968,9 @@ describe('Copay server', function() {
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['NewOutgoingTx', 'TxProposalFinallyAccepted', 'TxProposalAcceptedBy']);
// Check also events
server.emit.getCall(0).args[0].type.should.equal('TxProposalAcceptedBy');
server.emit.getCall(1).args[0].type.should.equal('TxProposalFinallyAccepted');;
server.emit.getCall(2).args[0].type.should.equal('NewOutgoingTx');
server._emit.getCall(0).args[1].type.should.equal('TxProposalAcceptedBy');
server._emit.getCall(1).args[1].type.should.equal('TxProposalFinallyAccepted');;
server._emit.getCall(2).args[1].type.should.equal('NewOutgoingTx');
done();
});