UNPKG

peer-exchange

Version:

Decentralized peer discovery and signaling

330 lines (302 loc) 10.9 kB
'use strict'; 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);