peer-exchange
Version:
Decentralized peer discovery and signaling
330 lines (302 loc) • 10.9 kB
JavaScript
;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var EventEmitter = require('events');
var old = require('old');
var RTCPeer = require('simple-peer');
var wrtc = require('get-browser-rtc')();
var onObject = require('on-object');
var assign = require('object-assign');
var debug = require('debug')('peer-exchange');
var once = require('once');
var Peer = require('pxp');
var floor = Math.floor;
var random = Math.random;
var Swarm = function (_EventEmitter) {
_inherits(Swarm, _EventEmitter);
function Swarm(networkId) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, Swarm);
if (!networkId || typeof networkId !== 'string') {
throw new Error('networkId must be a string');
}
var _this = _possibleConstructorReturn(this, (Swarm.__proto__ || Object.getPrototypeOf(Swarm)).call(this));
_this.networkId = networkId;
_this._peers = [];
_this.closed = false;
_this.allowIncoming = opts.allowIncoming != null ? opts.allowIncoming : true;
_this.wrtc = opts.wrtc || wrtc;
if (!_this.wrtc) {
throw new Error('No WebRTC implementation found, please pass one in ' + ' as the "wrtc" option (for example, the "wrtc" or ' + '"electron-webrtc" packages).');
}
_this.rtcConfig = opts.rtcConfig;
if (opts.getPeers) _this._getPeers = opts.getPeers;
if (_this.allowIncoming) {
_this.connectInfo = {
pxp: true,
relay: true,
webrtc: true
};
}
_this._error = _this._error.bind(_this);
return _this;
}
_createClass(Swarm, [{
key: '_error',
value: function _error(err) {
this.emit('error', err);
}
}, {
key: '_addPeer',
value: function _addPeer(peer) {
var _this2 = this;
this._peers.push(peer);
onObject(peer).once({
disconnect: function disconnect() {
var index = _this2._peers.indexOf(peer);
if (index === -1) return;
_this2._peers.splice(index, 1);
_this2.emit('disconnect', peer);
},
error: function error(err) {
return _this2.emit('peerError', err, peer);
}
});
if (peer.incoming) {
peer.once('connect:' + this.networkId, function (stream) {
_this2.emit('connect', stream, peer);
});
} else {
peer.connect(this.networkId, function (err, stream) {
if (err) return _this2._error(err);
_this2.emit('connect', stream, peer);
});
}
if (this.allowIncoming) {
peer.on('incoming', function (relay) {
debug('adding incoming peer');
var incomingPeer = _this2._createPeer(relay, {
incoming: true,
relayed: true
});
incomingPeer.once('upgrade', function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _this2._onUpgrade.apply(_this2, [incomingPeer].concat(args));
});
_this2._addPeer(incomingPeer);
});
}
this.emit('peer', peer);
}
}, {
key: 'connect',
value: function connect(stream, opts, cb) {
var _this3 = this;
if (this.closed) {
return cb(new Error('Swarm is closed'));
}
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
var peer = this._createPeer(stream, opts);
if (cb) peer.once('error', cb);
peer.onceReady(function () {
if (cb) peer.removeListener('error', cb);
_this3._addPeer(peer);
peer.once('connect:' + _this3.networkId, function (conn) {
conn.pxpPeer = peer;
if (cb) cb(null, conn);
});
});
}
}, {
key: 'accept',
value: function accept(stream) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var cb = arguments[2];
if (this.closed) {
return cb(new Error('Swarm is closed'));
}
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
opts.incoming = true;
this.connect(stream, opts, cb);
}
}, {
key: '_createPeer',
value: function _createPeer(stream) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var networks = this._getNetworks();
var connectInfo = this._getConnectInfo();
var peer = Peer(stream, networks, connectInfo, {
allowIncoming: this.allowIncoming
});
assign(peer, opts);
return peer;
}
}, {
key: '_getNetworks',
value: function _getNetworks() {
return _defineProperty({}, this.networkId, this._getPeers.bind(this));
}
}, {
key: '_getPeers',
value: function _getPeers(cb) {
// TODO: limit to random selection
cb(null, this._peers);
}
}, {
key: '_getConnectInfo',
value: function _getConnectInfo() {
return this.connectInfo;
}
}, {
key: '_onUpgrade',
value: function _onUpgrade(oldPeer, _ref2) {
var _this4 = this;
var transport = _ref2.transport;
var signal = _ref2.signal;
if (transport !== 'webrtc') {
var err = new Error('Peer requested upgrade via unknown transport: ' + ('"' + transport + '"'));
return oldPeer.error(err);
}
debug('upgrading peer: ' + transport);
var rtcConn = new RTCPeer({ wrtc: this.wrtc, config: this.rtcConfig });
this._signalRTC(oldPeer, rtcConn, function () {
_this4.accept(rtcConn, function (err) {
if (err) return _this4._error(err);
oldPeer.close();
});
});
rtcConn.signal(signal);
}
}, {
key: '_upgradePeer',
value: function _upgradePeer(oldPeer, cb) {
var _this5 = this;
var rtcConn = new RTCPeer({
wrtc: this.wrtc,
initiator: true,
config: this.rtcConfig
});
this._signalRTC(oldPeer, rtcConn, function (err) {
if (err) return cb(err);
_this5.connect(rtcConn, function (err, newPeer) {
if (err) return cb(err);
oldPeer.close();
cb(null, newPeer);
});
});
}
}, {
key: '_signalRTC',
value: function _signalRTC(peer, conn, cb) {
cb = once(cb);
conn.once('connect', function () {
return cb(null);
});
conn.once('error', function (err) {
return cb(err);
});
peer.on('upgrade', function (_ref3) {
var signal = _ref3.signal;
conn.signal(signal);
});
conn.on('signal', function (signal) {
peer.upgrade({ transport: 'webrtc', signal: signal });
});
}
}, {
key: 'getNewPeer',
value: function getNewPeer(cb) {
var _this6 = this;
cb = cb || function (err) {
if (err) _this6._error(err);
};
if (this.closed) {
return cb(new Error('Swarm is closed'));
}
if (this._peers.length === 0) {
return cb(new Error('Not connected to any peers'));
}
// TODO: smarter selection
var peer = this._peers[floor(random() * this._peers.length)];
peer.getPeers(this.networkId, function (err, candidates) {
if (err) return cb(err);
if (candidates.length === 0) {
return cb(new Error('Peer did not return any candidates'));
}
var candidate = candidates[floor(random() * candidates.length)];
if (candidate.connectInfo.pxp) {
_this6._relayAndUpgrade(peer, candidate, cb);
} else {
_this6._relay(peer, candidate, cb);
}
});
}
}, {
key: '_relayAndUpgrade',
value: function _relayAndUpgrade(peer, dest, cb) {
var _this7 = this;
cb = once(cb);
peer.relay(dest, function (err, relay) {
if (err) return cb(err);
var relayPeer = _this7._createPeer(relay, { relayed: true });
relayPeer.once('error', cb);
_this7._upgradePeer(relayPeer, cb);
});
}
}, {
key: '_relay',
value: function _relay(peer, dest, cb) {
var _this8 = this;
peer.relay(dest, function (err, relay) {
if (err) return cb(err);
_this8.emit('connect', relay);
return cb(null, relay);
});
}
}, {
key: 'close',
value: function close() {
this.closed = true;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this._peers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var peer = _step.value;
peer.close();
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
}, {
key: 'peers',
get: function get() {
return this._peers.slice(0);
}
}]);
return Swarm;
}(EventEmitter);
module.exports = old(Swarm);