Merge pull request #150 from isocolsky/feat/ws
Implement notifications via web sockets
This commit is contained in:
commit
b57a6ad88e
9
app.js
9
app.js
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var ExpressApp = require('./lib/expressapp');
|
var ExpressApp = require('./lib/expressapp');
|
||||||
|
var WsApp = require('./lib/wsapp');
|
||||||
|
|
||||||
var basePath = process.env.BWS_BASE_PATH || '/bws/api';
|
var basePath = process.env.BWS_BASE_PATH || '/bws/api';
|
||||||
var port = process.env.BWS_PORT || 3001;
|
var port = process.env.BWS_PORT || 3001;
|
||||||
|
@ -8,6 +9,12 @@ var port = process.env.BWS_PORT || 3001;
|
||||||
var app = ExpressApp.start({
|
var app = ExpressApp.start({
|
||||||
basePath: basePath,
|
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);
|
console.log('Bitcore Wallet Service running on port ' + port);
|
||||||
|
|
|
@ -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();
|
|
@ -37,6 +37,7 @@ Notification.create = function(opts) {
|
||||||
x.id = _.padLeft(now, 14, '0') + _.padLeft(opts.ticker || 0, 4, '0');
|
x.id = _.padLeft(now, 14, '0') + _.padLeft(opts.ticker || 0, 4, '0');
|
||||||
x.type = opts.type || 'general';
|
x.type = opts.type || 'general';
|
||||||
x.data = opts.data;
|
x.data = opts.data;
|
||||||
|
x.creatorId = opts.creatorId;
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
@ -48,6 +49,7 @@ Notification.fromObj = function(obj) {
|
||||||
x.id = obj.id;
|
x.id = obj.id;
|
||||||
x.type = obj.type,
|
x.type = obj.type,
|
||||||
x.data = obj.data;
|
x.data = obj.data;
|
||||||
|
x.creatorId = obj.creatorId;
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,9 +4,6 @@ var $ = require('preconditions').singleton();
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var log = require('npmlog');
|
var log = require('npmlog');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
var inherits = require('inherits');
|
|
||||||
var events = require('events');
|
|
||||||
var nodeutil = require('util');
|
|
||||||
|
|
||||||
var WalletUtils = require('bitcore-wallet-utils');
|
var WalletUtils = require('bitcore-wallet-utils');
|
||||||
var Bitcore = WalletUtils.Bitcore;
|
var Bitcore = WalletUtils.Bitcore;
|
||||||
|
@ -18,6 +15,7 @@ var Explorers = require('bitcore-explorers');
|
||||||
var ClientError = require('./clienterror');
|
var ClientError = require('./clienterror');
|
||||||
var Utils = require('./utils');
|
var Utils = require('./utils');
|
||||||
var Storage = require('./storage');
|
var Storage = require('./storage');
|
||||||
|
var EventBroadcaster = require('./eventbroadcaster');
|
||||||
|
|
||||||
var Wallet = require('./model/wallet');
|
var Wallet = require('./model/wallet');
|
||||||
var Copayer = require('./model/copayer');
|
var Copayer = require('./model/copayer');
|
||||||
|
@ -28,6 +26,7 @@ var Notification = require('./model/notification');
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var storage, blockExplorer;
|
var storage, blockExplorer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of the Bitcore Wallet Service.
|
* Creates an instance of the Bitcore Wallet Service.
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -41,8 +40,9 @@ function WalletService() {
|
||||||
this.notifyTicker = 0;
|
this.notifyTicker = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeutil.inherits(WalletService, events.EventEmitter);
|
WalletService.onNotification = function(func) {
|
||||||
|
EventBroadcaster.on('notification', func);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes global settings for all instances.
|
* Initializes global settings for all instances.
|
||||||
|
@ -161,6 +161,14 @@ WalletService.prototype._verifySignature = function(text, signature, pubKey) {
|
||||||
return WalletUtils.verifyMessage(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
|
* _notify
|
||||||
|
@ -180,9 +188,10 @@ WalletService.prototype._notify = function(type, data) {
|
||||||
type: type,
|
type: type,
|
||||||
data: data,
|
data: data,
|
||||||
ticker: this.notifyTicker++,
|
ticker: this.notifyTicker++,
|
||||||
|
creatorId: self.copayerId,
|
||||||
});
|
});
|
||||||
this.storage.storeNotification(walletId, n, function() {
|
this.storage.storeNotification(walletId, n, function() {
|
||||||
self.emit(n);
|
self._emit('notification', n);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
10
package.json
10
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": "0.0.13",
|
"version": "0.0.14",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"copay",
|
"copay",
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
"read": "^1.0.5",
|
"read": "^1.0.5",
|
||||||
"request": "^2.53.0",
|
"request": "^2.53.0",
|
||||||
"sjcl": "^1.0.2",
|
"sjcl": "^1.0.2",
|
||||||
|
"socket.io": "^1.3.5",
|
||||||
"uuid": "*"
|
"uuid": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -55,11 +56,8 @@
|
||||||
"contributors": [{
|
"contributors": [{
|
||||||
"name": "Ivan Socolsky",
|
"name": "Ivan Socolsky",
|
||||||
"email": "ivan@bitpay.com"
|
"email": "ivan@bitpay.com"
|
||||||
},
|
}, {
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Matias Alejo Garcia",
|
"name": "Matias Alejo Garcia",
|
||||||
"email": "ematiu@gmail.com"
|
"email": "ematiu@gmail.com"
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1611,6 +1611,7 @@ describe('Copay server', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
var last = _.last(notifications);
|
var last = _.last(notifications);
|
||||||
last.type.should.equal('TxProposalFinallyAccepted');
|
last.type.should.equal('TxProposalFinallyAccepted');
|
||||||
|
last.creatorId.should.equal(wallet.copayers[1].id);
|
||||||
last.data.txProposalId.should.equal(txp.id);
|
last.data.txProposalId.should.equal(txp.id);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -1868,6 +1869,9 @@ describe('Copay server', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
var types = _.pluck(notifications, 'type');
|
var types = _.pluck(notifications, 'type');
|
||||||
types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewTxProposal', 'NewAddress']);
|
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();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1945,7 +1949,7 @@ describe('Copay server', function() {
|
||||||
server.getPendingTxs({}, function(err, txs) {
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
var tx = txs[2];
|
var tx = txs[2];
|
||||||
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
|
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
|
||||||
sinon.spy(server, 'emit');
|
sinon.spy(server, '_emit');
|
||||||
server.signTx({
|
server.signTx({
|
||||||
txProposalId: tx.id,
|
txProposalId: tx.id,
|
||||||
signatures: signatures,
|
signatures: signatures,
|
||||||
|
@ -1964,9 +1968,9 @@ describe('Copay server', function() {
|
||||||
var types = _.pluck(notifications, 'type');
|
var types = _.pluck(notifications, 'type');
|
||||||
types.should.deep.equal(['NewOutgoingTx', 'TxProposalFinallyAccepted', 'TxProposalAcceptedBy']);
|
types.should.deep.equal(['NewOutgoingTx', 'TxProposalFinallyAccepted', 'TxProposalAcceptedBy']);
|
||||||
// Check also events
|
// Check also events
|
||||||
server.emit.getCall(0).args[0].type.should.equal('TxProposalAcceptedBy');
|
server._emit.getCall(0).args[1].type.should.equal('TxProposalAcceptedBy');
|
||||||
server.emit.getCall(1).args[0].type.should.equal('TxProposalFinallyAccepted');;
|
server._emit.getCall(1).args[1].type.should.equal('TxProposalFinallyAccepted');;
|
||||||
server.emit.getCall(2).args[0].type.should.equal('NewOutgoingTx');
|
server._emit.getCall(2).args[1].type.should.equal('NewOutgoingTx');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue