peer-exchange
Advanced tools
Comparing version 2.0.0 to 2.1.0
102
lib/swarm.js
@@ -29,3 +29,3 @@ 'use strict'; | ||
function Swarm(networkId) { | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -41,3 +41,3 @@ _classCallCheck(this, Swarm); | ||
_this.networkId = networkId; | ||
_this.peers = []; | ||
_this._peers = []; | ||
_this.closed = false; | ||
@@ -71,8 +71,8 @@ _this.allowIncoming = opts.allowIncoming != null ? opts.allowIncoming : true; | ||
this.peers.push(peer); | ||
this._peers.push(peer); | ||
onObject(peer).once({ | ||
disconnect: function disconnect() { | ||
var index = _this2.peers.indexOf(peer); | ||
var index = _this2._peers.indexOf(peer); | ||
if (index === -1) return; | ||
_this2.peers.splice(index, 1); | ||
_this2._peers.splice(index, 1); | ||
_this2.emit('disconnect', peer); | ||
@@ -101,3 +101,3 @@ }, | ||
}); | ||
incomingPeer.on('upgrade', function () { | ||
incomingPeer.once('upgrade', function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
@@ -131,3 +131,6 @@ args[_key] = arguments[_key]; | ||
_this3._addPeer(peer); | ||
if (cb) cb(null, peer); | ||
peer.once('connect:' + _this3.networkId, function (conn) { | ||
conn.pxpPeer = peer; | ||
if (cb) cb(null, conn); | ||
}); | ||
}); | ||
@@ -138,3 +141,3 @@ } | ||
value: function accept(stream) { | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var cb = arguments[2]; | ||
@@ -155,3 +158,3 @@ | ||
value: function _createPeer(stream) { | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -175,3 +178,3 @@ var networks = this._getNetworks(); | ||
// TODO: limit to random selection | ||
cb(null, this.peers); | ||
cb(null, this._peers); | ||
} | ||
@@ -185,25 +188,21 @@ }, { | ||
key: '_onUpgrade', | ||
value: function _onUpgrade(oldPeer, _ref2, res) { | ||
value: function _onUpgrade(oldPeer, _ref2) { | ||
var _this4 = this; | ||
var transport = _ref2.transport; | ||
var offer = _ref2.offer; | ||
var signal = _ref2.signal; | ||
if (transport !== 'webrtc') { | ||
var err = new Error('Peer requested upgrade via unknown transport: ' + ('"' + transport + '"')); | ||
res(err.message); | ||
return oldPeer.error(err); | ||
} | ||
debug('upgrading peer: ' + transport); | ||
var rtcConn = new RTCPeer({ wrtc: this.wrtc, trickle: false }); | ||
rtcConn.signal(offer); | ||
rtcConn.once('signal', function (answer) { | ||
rtcConn.once('connect', function () { | ||
_this4.connect(rtcConn, { incoming: true }, function (err) { | ||
if (err) return _this4._error(err); | ||
oldPeer.close(); | ||
}); | ||
var rtcConn = new RTCPeer({ wrtc: this.wrtc }); | ||
this._signalRTC(oldPeer, rtcConn, function () { | ||
_this4.accept(rtcConn, function (err) { | ||
if (err) return _this4._error(err); | ||
oldPeer.close(); | ||
}); | ||
res(null, answer); | ||
}); | ||
rtcConn.signal(signal); | ||
} | ||
@@ -217,19 +216,10 @@ }, { | ||
wrtc: this.wrtc, | ||
trickle: false, | ||
initiator: true | ||
}); | ||
rtcConn.once('signal', function (offer) { | ||
rtcConn.once('connect', function () { | ||
_this5.connect(rtcConn, function (err, newPeer) { | ||
if (err) return cb(err); | ||
oldPeer.close(); | ||
cb(null, newPeer); | ||
}); | ||
}); | ||
oldPeer.upgrade({ | ||
transport: 'webrtc', | ||
offer: offer | ||
}, function (err, answer) { | ||
this._signalRTC(oldPeer, rtcConn, function (err) { | ||
if (err) return cb(err); | ||
_this5.connect(rtcConn, function (err, newPeer) { | ||
if (err) return cb(err); | ||
rtcConn.signal(answer); | ||
oldPeer.close(); | ||
cb(null, newPeer); | ||
}); | ||
@@ -239,2 +229,21 @@ }); | ||
}, { | ||
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', | ||
@@ -244,10 +253,13 @@ value: function getNewPeer(cb) { | ||
cb = cb || function (err) { | ||
if (err) _this6._error(err); | ||
}; | ||
if (this.closed) { | ||
return cb(new Error('Swarm is closed')); | ||
} | ||
if (this.peers.length === 0) { | ||
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)]; | ||
var peer = this._peers[floor(random() * this._peers.length)]; | ||
peer.getPeers(this.networkId, function (err, candidates) { | ||
@@ -260,8 +272,3 @@ if (err) return cb(err); | ||
if (candidate.connectInfo.pxp) { | ||
_this6._relayAndUpgrade(peer, candidate, function (err, peer) { | ||
if (err) return cb(err); | ||
peer.once('connect:' + _this6.networkId, function (stream) { | ||
cb(null, stream); | ||
}); | ||
}); | ||
_this6._relayAndUpgrade(peer, candidate, cb); | ||
} else { | ||
@@ -305,3 +312,3 @@ _this6._relay(peer, candidate, cb); | ||
try { | ||
for (var _iterator = this.peers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
for (var _iterator = this._peers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var peer = _step.value; | ||
@@ -325,2 +332,7 @@ peer.close(); | ||
} | ||
}, { | ||
key: 'peers', | ||
get: function get() { | ||
return this._peers.slice(0); | ||
} | ||
}]); | ||
@@ -327,0 +339,0 @@ |
{ | ||
"name": "peer-exchange", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Decentralized peer discovery and signaling", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
144
README.md
@@ -9,4 +9,6 @@ # peer-exchange | ||
`peer-exchange` is a decentralized protocol for peer exchange and signaling. It reduces dependency on centralized peer exchange hubs, which may be useful in some applications. | ||
`peer-exchange` is a client for the Peer Exchange Protocol (PXP), a decentralized protocol for peer discovery and signaling. Rather than using centralized signal hubs, each node in the network exchanges peers and relays signaling data. | ||
This client uses WebRTC for peer connections, but you may also use any other transport by manually connecting and passing in a duplex stream. | ||
## Usage | ||
@@ -19,25 +21,24 @@ | ||
var ex = new Exchange('some-network-id') // pick some ID for your network | ||
var ex = new Exchange('some-network-id', { wrtc: wrtc }) | ||
// The network id can be any string unique to your network. | ||
// When using Node.js, the `wrtc` option is required. | ||
// (This can come from the 'wrtc' or 'electron-webrtc' packages). | ||
// optionally specify you want to accept incoming connections | ||
ex.accept('websocket', { port: 8000 }) | ||
ex.accept('tcp', { port: 8001 }) | ||
ex.accept('webrtc') | ||
ex.on('connect', (conn) => { | ||
// `conn` is a duplex stream multiplexed through the PXP connection, | ||
// which can be used for your P2P protocol. | ||
conn.pipe(something).pipe(conn) | ||
ex.on('peer', (peer) => { | ||
console.log('connected to peer:', peer.socket.transport, peer.remoteAddress) | ||
// We can query our current peers for a new peer by calling `getNewPeer()`. | ||
// `peer-exchange` will do the WebRTC signaling and connect to the peer. | ||
if (ex.peers.length < 8) ex.getNewPeer() | ||
}) | ||
// bootstrap by connecting to a few already-known initial peers | ||
ex.connect('websocket', '10.0.0.1', { port: 8000 }, (err, peer) => { ... }) | ||
ex.connect('tcp', '10.0.0.2', { port: 8000 }, (err, peer) => { | ||
// `peer` is a duplex stream | ||
// Bootstrap by connecting to one or more already-known PXP peers. | ||
// You can use any transport that creates a duplex stream (in this case TCP). | ||
var socket = net.connect(8000, '10.0.0.1', () => ex.connect(socket)) | ||
// now that we're connected, we can request more peers from our current peers. | ||
// this selects a peer at random and queries it for a new peer: | ||
ex.getNewPeer((err, peer) => { | ||
console.log('a random peer sent us a new peer:', peer.socket.transport, peer.remoteAddress) | ||
// `peer` is a duplex stream | ||
}) | ||
}) | ||
// You can optionally accept incoming connections using any transport. | ||
var server = net.createServer((socket) => ex.accept(socket)) | ||
server.listen(8000) | ||
``` | ||
@@ -62,19 +63,18 @@ | ||
`opts` can contain the following properties: | ||
`opts` should contain the following properties: | ||
- `wrtc`, *Object* - A WebRTC implementation for Node.js clients (e.g. [`wrtc`](https://github.com/js-platform/node-webrtc) or [`electron-webrtc`](https://github.com/mappum/electron-webrtc)). In browsers, the built-in implementation is used by default. | ||
- `transports`, *Object* - Manually specify connection transport interfaces. By default, the available transports are `websocket`, `tcp` (if in Node.js), and `webrtc` (if `wrtc` opt is supplied or if in browser). The built-in transports are exposed as `require('peer-exchange').transports`. | ||
---- | ||
#### `ex.connect(transport, host, opts, [callback])` | ||
#### `ex.connect(socket, [callback])` | ||
Manually connects to a peer. This is necessary to bootstrap our exchange with initial peers which we can query for additional peers. | ||
Manually adds a peer. This is necessary to bootstrap our exchange with initial peers which we can query for additional peers. This method is for *outgoing* connections, for *incoming* connections use `accept`. | ||
`transport` should be a string that specifies the name of the transport to connect with (e.g. `'websocket'` or `'tcp'`). | ||
`socket` should be a duplex stream that represents a connection to a peer which implements the Peer Exchange Protocol. | ||
`host` is the network address of the remote peer. | ||
`callback` will be called with `callback(err, connection)`, where `connection` is a duplex stream which may be used by your application for your P2P protocol. | ||
`opts` is an object containing transport options (e.g. `{ port: 8000 }`). | ||
---- | ||
#### `ex.accept(socket, [callback])` | ||
`callback` will be called with | ||
`callback(err, peer)`. | ||
Similar to `connect`, but used with *incoming* peer connections. | ||
@@ -84,30 +84,14 @@ ---- | ||
Randomly selects a peer we are connected to, and queries it for a new peer. A connection will be made with the new peer, using the already-connected peer as a relay (for signaling, NAT traversal, etc.). | ||
Queries out current peers for a new peer, then connects to it via WebRTC. The already-connected peer will act as a relay for signaling. | ||
This will error if our exchange is not yet connected to any peers. | ||
The `'connect'` event will be emitted once the connection is established. | ||
`callback` will be called with `callback(err, peer)`. | ||
This will error if our exchange is not connected to any peers. | ||
---- | ||
#### `ex.accept(transport, opts, [callback])` | ||
`callback` will be called with `callback(err, connection)`. | ||
Begins accepting incoming peer connections on a transport. | ||
`transport` should be a string that specifies the name of the transport to accept connections with (e.g. `'websocket'`, `'webrtc'`, or `'tcp'`). | ||
`opts` is an object containing transport options (e.g. `{ port: 8000 }`). | ||
`callback` is called with `callback(err)` when the exchange is ready to accept incoming connections (or when an error occurs during setup). | ||
---- | ||
#### `ex.unaccept(transport)` | ||
Stops accepting incoming peer connections on a transport. | ||
`transport` should be a string that specifies the name of the transport to accept connections with (e.g. `'websocket'`, `'webrtc'`, or `'tcp'`). | ||
---- | ||
#### `ex.close([callback])` | ||
Closes all peer connections in the exchange and stops accepting incoming connections. | ||
Closes all peer connections in the exchange and prevents adding any new connections. | ||
@@ -122,5 +106,5 @@ `callback` is called with `callback(err)` when the exchange is closed (or when an error occurs). | ||
An array of connected peers. Useful for iterating through peers or getting the number of connections, but mutating this array will cause undefined behavior. | ||
An array of connected peers. Useful for iterating through peers or getting the number of connections. | ||
#### `ex.id` | ||
#### `ex.networkId` | ||
@@ -133,3 +117,3 @@ The network ID provided in the constructor. | ||
#### `ex.on('peer', function (peer) { ... })` | ||
#### `ex.on('connect', function (conn) { ... })` | ||
@@ -144,54 +128,2 @@ Emitted whenever a new peer connection is established (both incoming and outgoing). | ||
## Peer | ||
`Peer` objects are returned by `Exchange#connect()`, `Exchange#getNewPeer()`, and `Exchange#on('peer')`. | ||
`Peer` is a duplex stream, which streams data over the transport to/from the remote peer. | ||
### Methods | ||
#### `peer.getNewPeer([callback])` | ||
Queries this peer for a new peer. A connection will be made with the new peer, using the already-connected peer as a relay (for signaling, NAT traversal, etc.). | ||
`callback` will be called with `callback(err, peer)`. | ||
---- | ||
#### `peer.destroy()` | ||
Closes this peer connection and frees its resources. | ||
---- | ||
#### Properties | ||
#### `peer.socket` | ||
The transport DuplexStream for this peer connection. | ||
#### `peer.socket.transport` | ||
A string containing the name of the transport this connection is using. | ||
#### `peer.remoteAddress` | ||
A string containing the network address of the remote peer. | ||
---- | ||
#### Events | ||
#### `ex.on('close', function () { ... })` | ||
Emitted when the connection closes. | ||
#### `ex.on('error', function (err) { ... })` | ||
Emitted when an error occurs. | ||
---- | ||
## Transport Interface | ||
**TODO** | ||
(See `src/transports.js` for now) | ||
## Security Notes | ||
@@ -201,3 +133,3 @@ | ||
It is recommended to use an authenticated transport (e.g. 'wss') for initial nodes to prevent MITM (attackers would be able to control all your peers, which can be very bad in some applications). | ||
It is recommended to use an authenticated transport when possible (e.g. WebSockets over HTTPS) for initial bootstrapping to prevent man-in-the-middle attacks (attackers could control all the peers you connect to, which can be very bad in some applications). | ||
@@ -208,2 +140,2 @@ ## Comparison with `signalhub` | ||
Note that `signalhub` may be better suited for some applications, for instance when connecting to peers in small swarms when no peer addresses are initially known (e.g. torrent swarms). In the future, a DHT could help with finding initial peers for this sort of use case. | ||
Note that `signalhub` may be better suited for some applications, for instance when connecting to peers in small swarms when no peer addresses are initially known (e.g. BitTorrent swarms). In the future, a DHT could help with finding initial peers for this sort of use case. |
@@ -30,5 +30,5 @@ var test = require('tape') | ||
var peer2 = RTCPeer({ wrtc: wrtc }) | ||
peer1.on('signal', (data) => peer2.signal(data)) | ||
peer2.on('signal', (data) => peer1.signal(data)) | ||
var maybeDone = () => { | ||
peer1.on('signal', function (data) { peer2.signal(data) }) | ||
peer2.on('signal', function (data) { peer1.signal(data) }) | ||
var maybeDone = function () { | ||
if (!peer1.connected || !peer2.connected) return | ||
@@ -35,0 +35,0 @@ cb(null, [ peer1, peer2 ]) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
688
0
53327
133