copay/js/models/network/WebRTC.js

444 lines
12 KiB
JavaScript
Raw Normal View History

2014-04-08 14:35:43 -07:00
var imports = require('soop').imports();
var EventEmitter= imports.EventEmitter || require('events').EventEmitter;
2014-04-23 09:44:20 -07:00
var bitcore = require('bitcore');
var util = bitcore.util;
var extend = require('util')._extend;
/*
* Emits
2014-05-09 10:35:57 -07:00
* 'connect'
* when network layout has change (new/lost peers, etc)
*
* 'data'
* when an unknown data type arrives
*
* Provides
2014-04-23 17:20:44 -07:00
* send(toPeerIds, {data}, cb?)
*
*/
2014-04-08 14:35:43 -07:00
2014-04-14 11:31:10 -07:00
function Network(opts) {
2014-04-30 08:58:40 -07:00
var self = this;
2014-04-24 19:13:55 -07:00
opts = opts || {};
this.apiKey = opts.apiKey || 'lwjd5qra8257b9';
this.debug = opts.debug || 3;
this.maxPeers = opts.maxPeers || 10;
2014-04-29 15:51:49 -07:00
this.sjclParams = opts.sjclParams || {
2014-04-29 16:56:28 -07:00
salt: 'f28bfb49ef70573c',
2014-04-29 15:51:49 -07:00
iter:500,
mode:'ccm',
ts:parseInt(64),
};
2014-05-05 11:54:17 -07:00
this.opts = {};
['config', 'port', 'host', 'path', 'debug', 'key'].forEach(function(k) {
2014-04-23 17:20:44 -07:00
if (opts[k]) self.opts[k] = opts[k];
2014-04-17 12:58:07 -07:00
});
2014-04-24 19:13:55 -07:00
this.cleanUp();
2014-04-08 14:35:43 -07:00
}
2014-04-23 17:20:44 -07:00
Network.parent = EventEmitter;
2014-04-08 14:35:43 -07:00
2014-04-24 19:13:55 -07:00
Network.prototype.cleanUp = function() {
this.started = false;
this.connectedPeers = [];
this.peerId = null;
2014-04-30 08:58:40 -07:00
this.netKey = null;
2014-04-24 19:13:55 -07:00
this.copayerId = null;
this.signingKey = null;
this.allowedCopayerIds=null;
2014-05-01 17:48:17 -07:00
this.isInboundPeerAuth=[];
2014-04-24 19:13:55 -07:00
this.copayerForPeer={};
this.connections={};
if (this.peer) {
console.log('## DESTROYING PEER INSTANCE'); //TODO
this.peer.disconnect();
this.peer.destroy();
this.peer = null;
}
this.closing = 0;
};
Network.parent=EventEmitter;
2014-04-08 14:35:43 -07:00
// Array helpers
2014-04-14 11:31:10 -07:00
Network._arrayDiff = function(a, b) {
2014-04-08 14:35:43 -07:00
var seen = [];
var diff = [];
for (var i = 0; i < b.length; i++)
seen[b[i]] = true;
for (var j = 0; j < a.length; j++)
if (!seen[a[j]])
diff.push(a[j]);
return diff;
};
2014-04-14 11:31:10 -07:00
Network._inArray = function(el, array) {
2014-04-08 14:35:43 -07:00
return array.indexOf(el) > -1;
};
2014-04-14 11:31:10 -07:00
Network._arrayPushOnce = function(el, array) {
2014-04-08 14:35:43 -07:00
var ret = false;
2014-04-14 11:31:10 -07:00
if (!Network._inArray(el, array)) {
2014-04-08 14:35:43 -07:00
array.push(el);
ret = true;
}
return ret;
};
2014-04-14 11:31:10 -07:00
Network._arrayRemove = function(el, array) {
2014-04-08 14:35:43 -07:00
var pos = array.indexOf(el);
if (pos >= 0) array.splice(pos, 1);
return array;
};
2014-04-23 09:44:20 -07:00
Network.prototype.connectedCopayers = function() {
var ret =[];
for(var i in this.connectedPeers){
var copayerId =this.copayerForPeer[this.connectedPeers[i]];
if (copayerId) ret.push(copayerId);
}
return ret;
};
2014-04-24 19:13:55 -07:00
Network.prototype._deletePeer = function(peerId) {
2014-05-01 12:40:25 -07:00
console.log('### Deleting connection from peer:', peerId);
2014-05-01 17:48:17 -07:00
delete this.isInboundPeerAuth[peerId];
2014-05-01 12:40:25 -07:00
delete this.copayerForPeer[peerId];
2014-04-24 19:13:55 -07:00
if (this.connections[peerId]) {
this.connections[peerId].close();
}
2014-04-20 16:24:24 -07:00
delete this.connections[peerId];
2014-04-14 11:31:10 -07:00
this.connectedPeers = Network._arrayRemove(peerId, this.connectedPeers);
2014-04-24 19:13:55 -07:00
};
2014-05-09 10:44:05 -07:00
Network.prototype._onClose = function(peerID) {
this._deletePeer(peerID);
this.emit('disconnect', peerID);
2014-04-08 14:35:43 -07:00
};
2014-05-01 17:48:17 -07:00
Network.prototype.connectToCopayers = function(copayerIds) {
2014-04-08 14:35:43 -07:00
var self = this;
2014-04-23 12:02:23 -07:00
var arrayDiff= Network._arrayDiff(copayerIds, this.connectedCopayers());
2014-05-01 12:40:25 -07:00
2014-04-23 12:02:23 -07:00
arrayDiff.forEach(function(copayerId) {
2014-05-01 12:40:25 -07:00
if (this.allowedCopayerIds && !this.allowedCopayerIds[copayerId]) {
console.log('### IGNORING STRANGE COPAYER:', copayerId);
this._deletePeer(this.peerFromCopayer(copayerId));
2014-05-09 10:35:57 -07:00
} else {
2014-05-01 12:40:25 -07:00
console.log('### CONNECTING TO:', copayerId);
self.connectTo(copayerId);
}
2014-04-08 14:35:43 -07:00
});
};
2014-04-23 18:43:17 -07:00
Network.prototype._sendHello = function(copayerId) {
2014-04-24 19:13:55 -07:00
console.log('### SENDING HELLO TO ', copayerId);
2014-04-23 09:44:20 -07:00
this.send(copayerId, {
2014-04-23 18:43:17 -07:00
type: 'hello',
2014-04-23 09:44:20 -07:00
copayerId: this.copayerId,
});
};
2014-05-01 17:48:17 -07:00
Network.prototype._addConnectedCopayer = function(copayerId, isInbound) {
2014-04-23 12:02:23 -07:00
var peerId = this.peerFromCopayer(copayerId);
this._addCopayerMap(peerId,copayerId);
2014-05-01 17:48:17 -07:00
Network._arrayPushOnce(peerId, this.connectedPeers);
2014-05-09 11:02:57 -07:00
this.emit('connect', copayerId);
2014-04-23 09:44:20 -07:00
};
2014-04-29 15:51:49 -07:00
Network.prototype._onData = function(encStr, isInbound, peerId) {
2014-04-23 18:43:17 -07:00
var sig, payload;
2014-04-25 13:36:00 -07:00
2014-04-09 07:05:25 -07:00
try {
2014-04-29 15:51:49 -07:00
var data = this._decrypt(encStr);
payload= JSON.parse(data);
2014-04-09 07:05:25 -07:00
} catch (e) {
2014-04-24 19:13:55 -07:00
console.log('### ERROR IN DATA: "%s" ', data, isInbound, e);
this._deletePeer(peerId);
2014-04-09 07:05:25 -07:00
return;
2014-04-25 13:36:00 -07:00
}
2014-04-23 18:43:17 -07:00
2014-04-24 19:13:55 -07:00
console.log('### RECEIVED INBOUND?:%s TYPE: %s FROM %s',
isInbound, payload.type, peerId, payload);
2014-04-23 09:44:20 -07:00
2014-05-01 17:48:17 -07:00
if(isInbound && payload.type === 'hello') {
2014-05-01 12:40:25 -07:00
var payloadStr = JSON.stringify(payload);
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
console.log('#### Peer sent HELLO but it is not on the allowedCopayerIds. Closing connection',
this.allowedCopayerIds, payload.copayerId);
this._deletePeer(peerId);
return;
2014-04-23 18:43:17 -07:00
}
2014-05-01 12:40:25 -07:00
console.log('#### Peer sent hello. Setting it up.'); //TODO
2014-05-01 17:48:17 -07:00
this._addConnectedCopayer(payload.copayerId, isInbound);
this._setInboundPeerAuth(peerId, true);
2014-04-23 18:43:17 -07:00
return;
2014-04-23 09:44:20 -07:00
}
2014-04-08 14:35:43 -07:00
2014-05-01 17:48:17 -07:00
if ( !this.copayerForPeer[peerId] || (isInbound && !this.isInboundPeerAuth[peerId]) ) {
2014-04-24 19:13:55 -07:00
this._deletePeer(peerId);
2014-04-23 09:44:20 -07:00
return;
}
2014-04-23 18:43:17 -07:00
2014-04-24 19:13:55 -07:00
var self=this;
2014-04-23 18:43:17 -07:00
switch(payload.type) {
2014-04-08 14:35:43 -07:00
case 'disconnect':
2014-04-23 18:43:17 -07:00
this._onClose(peerId);
break;
default:
2014-04-23 18:43:17 -07:00
this.emit('data', self.copayerForPeer[peerId], payload, isInbound);
2014-04-08 14:35:43 -07:00
}
};
2014-04-17 12:50:48 -07:00
Network.prototype._checkAnyPeer = function() {
if (!this.connectedPeers.length) {
console.log('EMIT openError: no more peers, not even you!');
2014-04-24 19:13:55 -07:00
this.cleanUp();
2014-04-17 12:50:48 -07:00
this.emit('openError');
}
2014-04-24 19:13:55 -07:00
if (this.connectedPeers.length === 1) {
this.emit('onlyYou');
}
};
2014-04-17 12:50:48 -07:00
2014-05-01 17:48:17 -07:00
Network.prototype._setupConnectionHandlers = function(dataConn, toCopayerId) {
2014-04-23 17:20:44 -07:00
var self = this;
2014-04-08 14:35:43 -07:00
2014-05-01 17:48:17 -07:00
var isInbound = toCopayerId ? false : true;
2014-04-08 14:35:43 -07:00
dataConn.on('open', function() {
2014-05-01 17:48:17 -07:00
if (!Network._inArray(dataConn.peer, self.connectedPeers) &&
!self.connections[dataConn.peer]) {
2014-04-23 12:02:23 -07:00
2014-04-20 16:24:24 -07:00
self.connections[dataConn.peer] = dataConn;
2014-04-08 14:35:43 -07:00
2014-04-23 18:43:17 -07:00
console.log('### DATA CONNECTION READY: %s (inbound: %s) AUTHENTICATING...',
2014-04-08 14:35:43 -07:00
dataConn.peer, isInbound);
// The connecting peer send hello
2014-05-01 17:48:17 -07:00
if(toCopayerId) {
self.emit('connected');
2014-05-01 17:48:17 -07:00
self._sendHello(toCopayerId);
2014-05-09 11:02:57 -07:00
self._addConnectedCopayer(toCopayerId);
2014-05-01 17:48:17 -07:00
}
2014-04-08 14:35:43 -07:00
}
});
dataConn.on('data', function(data) {
2014-04-23 09:44:20 -07:00
self._onData(data, isInbound, dataConn.peer);
2014-04-08 14:35:43 -07:00
});
dataConn.on('error', function(e) {
2014-04-23 17:20:44 -07:00
console.log('### DATA ERROR', e); //TODO
2014-04-20 16:24:24 -07:00
self._onClose(dataConn.peer);
self._checkAnyPeer();
2014-04-17 12:50:48 -07:00
self.emit('dataError');
2014-04-08 14:35:43 -07:00
});
dataConn.on('close', function() {
if (self.closing) return;
2014-04-20 16:24:24 -07:00
2014-04-23 17:20:44 -07:00
console.log('### CLOSE RECV FROM:', dataConn.peer);
2014-04-08 14:35:43 -07:00
self._onClose(dataConn.peer);
2014-04-17 12:50:48 -07:00
self._checkAnyPeer();
2014-04-08 14:35:43 -07:00
});
};
2014-04-14 11:31:10 -07:00
Network.prototype._setupPeerHandlers = function(openCallback) {
2014-04-23 17:20:44 -07:00
var self = this;
2014-04-08 14:35:43 -07:00
var p = this.peer;
2014-04-20 08:41:28 -07:00
p.on('open', function() {
self.connectedPeers = [self.peerId];
2014-04-23 12:02:23 -07:00
self.copayerForPeer[self.peerId]= self.copayerId;
2014-04-20 08:41:28 -07:00
return openCallback();
2014-04-08 14:35:43 -07:00
});
p.on('error', function(err) {
if (!err.message.match(/Could\snot\sconnect\sto peer/)) {
console.log('### PEER ERROR:', err);
self.emit('error', err);
}
2014-04-17 12:50:48 -07:00
self._checkAnyPeer();
2014-04-08 14:35:43 -07:00
});
2014-04-24 13:54:43 -07:00
p.on('connection', function(dataConn) {
2014-04-09 07:05:25 -07:00
console.log('### NEW INBOUND CONNECTION %d/%d', self.connectedPeers.length, self.maxPeers);
if (self.connectedPeers.length >= self.maxPeers) {
console.log('### PEER REJECTED. PEER MAX LIMIT REACHED');
dataConn.on('open', function() {
console.log('### CLOSING CONN FROM:' + dataConn.peer);
dataConn.close();
});
2014-04-23 17:20:44 -07:00
} else {
2014-05-01 17:48:17 -07:00
self._setInboundPeerAuth(dataConn.peer, false);
self._setupConnectionHandlers(dataConn);
2014-04-09 07:05:25 -07:00
}
2014-04-08 14:35:43 -07:00
});
};
2014-04-23 12:02:23 -07:00
Network.prototype._addCopayerMap = function(peerId, copayerId) {
if (!this.copayerForPeer[peerId]) {
2014-04-24 19:13:55 -07:00
if(Object.keys(this.copayerForPeer).length < this.maxPeers) {
console.log('Adding peer/copayer', peerId, copayerId); //TODO
this.copayerForPeer[peerId]=copayerId;
}
else {
console.log('### maxPeerLimit of %d reached. Refusing to add more copayers.', this.maxPeers); //TODO
}
2014-04-23 12:02:23 -07:00
}
};
2014-04-24 19:13:55 -07:00
2014-05-01 17:48:17 -07:00
Network.prototype._setInboundPeerAuth = function(peerId, isAuthenticated) {
this.isInboundPeerAuth[peerId] = isAuthenticated;
2014-04-24 19:13:55 -07:00
};
2014-04-23 09:44:20 -07:00
Network.prototype.setCopayerId = function(copayerId) {
2014-04-20 08:41:28 -07:00
if (this.started) {
2014-04-23 17:20:44 -07:00
throw new Error('network already started: can not change peerId')
2014-04-20 08:41:28 -07:00
}
2014-04-23 09:44:20 -07:00
this.copayerId = copayerId;
2014-04-23 18:43:17 -07:00
this.copayerIdBuf = new Buffer(copayerId,'hex');
2014-04-23 09:44:20 -07:00
this.peerId = this.peerFromCopayer(this.copayerId);
2014-04-23 12:02:23 -07:00
this._addCopayerMap(this.peerId,copayerId);
2014-04-20 08:41:28 -07:00
};
2014-04-23 18:43:17 -07:00
2014-04-23 09:44:20 -07:00
Network.prototype.peerFromCopayer = function(hex) {
2014-05-01 17:52:46 -07:00
// TODO cache this.
2014-04-24 19:13:55 -07:00
var SIN = bitcore.SIN;
return new SIN(new Buffer(hex,'hex')).toString();
2014-04-23 09:44:20 -07:00
};
2014-04-20 08:41:28 -07:00
2014-04-23 18:43:17 -07:00
Network.prototype.start = function(opts, openCallback) {
2014-04-18 14:25:51 -07:00
opts = opts || {};
2014-04-24 19:13:55 -07:00
if (this.started) return openCallback();
2014-04-30 08:58:40 -07:00
this.netKey = opts.netKey;
2014-04-24 19:13:55 -07:00
this.maxPeers = opts.maxPeers || this.maxPeers;
2014-04-16 18:12:30 -07:00
if (opts.token)
this.opts.token = opts.token;
if (!this.copayerId)
2014-04-23 18:43:17 -07:00
this.setCopayerId(opts.copayerId);
2014-04-23 09:44:20 -07:00
console.log('CREATING PEER INSTANCE:', this.peerId); //TODO
2014-04-17 12:58:07 -07:00
this.peer = new Peer(this.peerId, this.opts);
2014-04-18 10:40:16 -07:00
this.started = true;
2014-05-01 17:48:17 -07:00
this._setupPeerHandlers(openCallback);
2014-04-08 14:35:43 -07:00
};
2014-04-23 09:44:20 -07:00
2014-04-24 12:07:49 -07:00
Network.prototype.getOnlinePeerIDs = function() {
return this.connectedPeers;
};
Network.prototype.getPeer = function() {
return this.peer;
2014-04-23 18:43:17 -07:00
};
2014-04-30 08:58:40 -07:00
Network.prototype._encrypt = function(payloadStr) {
2014-04-29 15:51:49 -07:00
var plainText = sjcl.codec.utf8String.toBits(payloadStr);
2014-04-30 08:58:40 -07:00
var p = this.sjclParams;
ct = sjcl.encrypt(this.netKey, plainText, p);//,p, rp);
2014-04-29 15:51:49 -07:00
var c = JSON.parse(ct);
var toSend = {
iv: c.iv,
ct: c.ct,
};
return JSON.stringify(toSend);
};
Network.prototype._decrypt = function(encStr) {
var i = JSON.parse(encStr);
for (var k in this.sjclParams) {
i[k] = this.sjclParams[k];
}
var str= JSON.stringify(i);
2014-04-30 08:58:40 -07:00
var pt = sjcl.decrypt(this.netKey, str);
2014-04-29 15:51:49 -07:00
return pt;
};
2014-04-25 13:36:00 -07:00
2014-04-30 09:02:40 -07:00
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
2014-04-23 09:44:20 -07:00
var peerId = this.peerFromCopayer(copayerId);
2014-04-08 14:35:43 -07:00
if (peerId !== this.peerId) {
2014-04-20 16:24:24 -07:00
var dataConn = this.connections[peerId];
if (dataConn) {
2014-04-30 09:02:40 -07:00
dataConn.send(payload);
2014-04-20 16:24:24 -07:00
}
else {
2014-04-24 07:58:29 -07:00
console.log('[WebRTC.js.255] WARN: NO CONNECTION TO:', peerId); //TODO
2014-04-08 14:35:43 -07:00
}
}
if (typeof cb === 'function') cb();
};
2014-04-23 18:43:17 -07:00
Network.prototype.send = function(copayerIds, payload, cb) {
2014-04-08 14:35:43 -07:00
var self=this;
2014-04-23 09:44:20 -07:00
if (!copayerIds) {
copayerIds = this.connectedCopayers();
2014-04-23 18:43:17 -07:00
payload.isBroadcast = 1;
}
2014-04-24 19:13:55 -07:00
var sig;
2014-04-25 13:36:00 -07:00
var payloadStr = JSON.stringify(payload);
2014-04-30 09:02:40 -07:00
var encPayload = this._encrypt(payloadStr);
2014-04-23 09:44:20 -07:00
if (Array.isArray(copayerIds)) {
var l = copayerIds.length;
2014-04-08 14:35:43 -07:00
var i = 0;
2014-04-23 09:44:20 -07:00
copayerIds.forEach(function(copayerId) {
2014-04-30 09:02:40 -07:00
self._sendToOne(copayerId, encPayload, sig, function () {
2014-04-08 14:35:43 -07:00
if (++i === l && typeof cb === 'function') cb();
});
});
}
2014-04-23 09:44:20 -07:00
else if (typeof copayerIds === 'string')
2014-04-30 09:02:40 -07:00
self._sendToOne(copayerIds, encPayload, sig, cb);
2014-04-08 14:35:43 -07:00
};
2014-05-01 17:48:17 -07:00
2014-04-23 09:44:20 -07:00
Network.prototype.connectTo = function(copayerId) {
2014-04-08 14:35:43 -07:00
var self = this;
2014-04-23 12:02:23 -07:00
2014-05-01 17:48:17 -07:00
var peerId = this.peerFromCopayer(copayerId);
2014-04-24 19:13:55 -07:00
console.log('### STARTING CONNECTION TO:\n\t'+ peerId+"\n\t"+ copayerId);
2014-04-08 14:35:43 -07:00
var dataConn = this.peer.connect(peerId, {
serialization: 'none',
reliable: true,
});
2014-05-01 17:48:17 -07:00
self._setupConnectionHandlers(dataConn, copayerId);
2014-04-08 14:35:43 -07:00
};
2014-04-24 19:13:55 -07:00
Network.prototype.lockIncommingConnections = function(allowedCopayerIdsArray) {
if (!this.allowedCopayerIds)
console.log('[webrtc] #### LOCKING INCOMMING CONNECTIONS');
this.allowedCopayerIds={};
for(var i in allowedCopayerIdsArray) {
2014-05-09 11:02:57 -07:00
this.allowedCopayerIds[ allowedCopayerIdsArray[i] ] = true;
}
};
2014-04-18 14:25:51 -07:00
Network.prototype.disconnect = function(cb, forced) {
2014-04-08 14:35:43 -07:00
var self = this;
self.closing = 1;
2014-04-24 13:54:43 -07:00
self.send(null, { type: 'disconnect' }, function(){
2014-04-24 19:13:55 -07:00
self.cleanUp();
2014-04-24 13:54:43 -07:00
if (typeof cb === 'function') cb();
});
2014-04-08 14:35:43 -07:00
};
2014-04-14 11:31:10 -07:00
module.exports = require('soop')(Network);