diff --git a/README.md b/README.md index 31d04266de..a632d28ca5 100644 --- a/README.md +++ b/README.md @@ -56,23 +56,148 @@ npm install --save libp2p ## Usage -> **Disclaimer - We haven't solidified [libp2p interface](https://github.com/libp2p/interface-libp2p) yet, it might change at anytime.** - ### Extending libp2p skeleton libp2p becomes very simple and basically acts as a glue for every module that compose this library. Since it can be highly customized, it requires some setup. What we recommend is to have a libp2p build for the system you are developing taking into account in your needs (e.g. for a browser working version of libp2p that acts as the network layer of IPFS, we have a built and minified version that browsers can require). -### libp2p API +**Example:** + +```JavaScript +// Creating a bundle that adds: +// transport: websockets + tcp +// stream-muxing: SPDY +// crypto-channel: secio +// discovery: multicast-dns + +const libp2p = require('libp2p') +const TCP = require('libp2p-tcp') +const WS = require('libp2p-websockets') +const spdy = require('libp2p-spdy') +const secio = require('libp2p-secio') +const MulticastDNS = require('libp2p-mdns') + +class Node extends libp2p { + constructor (peerInfo, peerBook, options) { + options = options || {} + + const modules = { + transport: [ + new TCP(), + new WS() + ], + connection: { + muxer: [ + spdy + ], + crypto: [ + secio + ] + }, + discovery: [ + new MulticastDNS(peerInfo, 'your-identifier') + ] + } + + super(modules, peerInfo, peerBook, options) + } +} + +// Now all the nodes you create, will have TCP, WebSockets, SPDY, SECIO and MulticastDNS support. +``` -Defined by [interface-libp2p](https://github.com/libp2p/interface-libp2p) +### API -## Development +#### Create a Node - `new libp2p.Node([peerInfo, peerBook, options])` -## Linting +> Creates an instance of the libp2p.Node. + +- `peerInfo`: instance of [PeerInfo][] that contains the [PeerId][], Keys and [multiaddrs][multiaddr] of the libp2p Node. Optional. +- `peerBook`: instance of [PeerBook][] that contains the [PeerInfo][] of known peers. Optional. +- `options`: Object containing custom options for the bundle. + +#### `libp2p.start(callback)` + +> Start the libp2p Node. + +`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case starting the node fails. + +#### `libp2p.stop(callback)` + +> Stop the libp2p Node. + +`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case stopping the node fails. + +#### `libp2p.dial(peer [, protocol, callback])` + +> Dials to another peer in the network. + +- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][] +- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') + +`callback` is a function with the following `function (err, conn) {}` signature, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined. + +#### `libp2p.hangUp(peer, callback) + +> Closes an open connection with a peer, graciously. + +- `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][] + +`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case stopping the node fails. + +#### `libp2p.handle(protocol, handlerFunc [, matchFunc])` + +> Handle new protocol + +- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') +- `handlerFunc`: Function with signature `function (protocol, conn) {}` +- `matchFunc`: Function for matching on protocol (exact matching, semver, etc). Default to exact match. + +#### `libp2p.unhandle(protocol) + +> Stop handling protocol + +- `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') + +#### `libp2p.on('peer', (peer) => {})` + +> Peer has been discovered. + +- `peer`: instance of [PeerInfo][] + +#### `libp2p.isOn()` + +> Check if libp2p is started + +#### `libp2p.ping(peer [, options], callback)` + +> Ping a node in the network + +#### `libp2p.peerBook` + +> PeerBook instance of the node + +#### `libp2p.peerInfo` + +> PeerInfo instance of the node + +--------------------- + +`SOON™` + +#### `libp2p.findPeers` + +#### `libp2p.findProviders` + +#### `libp2p.record.put` + +#### `libp2p.record.get` + +[PeerInfo]: https://github.com/libp2p/js-peer-info +[PeerId]: https://github.com/libp2p/js-peer-id +[PeerBook]: https://github.com/libp2p/js-peer-book +[multiaddr]: https://github.com/multiformats/js-multiaddr +[Connection]: https://github.com/libp2p/interface-connection -```sh -> npm run lint -``` ### Packages diff --git a/package.json b/package.json index 3d0a4757cf..43f0bc6009 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "homepage": "https://github.com/libp2p/js-libp2p", "devDependencies": { - "aegir": "^11.0.0", + "aegir": "^11.0.1", "chai": "^3.5.0", "dirty-chai": "^1.2.2", "pre-commit": "^1.2.2" @@ -42,11 +42,10 @@ "dependencies": { "libp2p-ping": "~0.3.2", "libp2p-swarm": "~0.26.19", - "mafmt": "^2.1.6", - "multiaddr": "^2.2.2", + "multiaddr": "^2.2.3", "peer-book": "~0.3.1", - "peer-id": "~0.8.4", - "peer-info": "~0.8.4" + "peer-id": "~0.8.5", + "peer-info": "~0.8.5" }, "contributors": [ "David Dias ", @@ -55,4 +54,4 @@ "greenkeeperio-bot ", "mayerwin " ] -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index 47984055ce..3719d9e7f3 100644 --- a/src/index.js +++ b/src/index.js @@ -5,8 +5,7 @@ const PeerId = require('peer-id') const PeerInfo = require('peer-info') const PeerBook = require('peer-book') const multiaddr = require('multiaddr') -const mafmt = require('mafmt') -const EE = require('events').EventEmitter +const EventEmitter = require('events').EventEmitter const assert = require('assert') const Ping = require('libp2p-ping') const setImmediate = require('async/setImmediate') @@ -14,10 +13,10 @@ const setImmediate = require('async/setImmediate') exports = module.exports const OFFLINE_ERROR_MESSAGE = 'The libp2p node is not started yet' -const IPFS_CODE = 421 -class Node { +class Node extends EventEmitter { constructor (_modules, _peerInfo, _peerBook, _options) { + super() assert(_modules, 'requires modules to equip libp2p with features') assert(_peerInfo, 'requires a PeerInfo instance') @@ -26,8 +25,6 @@ class Node { this.peerBook = _peerBook || new PeerBook() this.isOnline = false - this.discovery = new EE() - this.swarm = new Swarm(this.peerInfo) // Attach stream multiplexers @@ -66,9 +63,7 @@ class Node { let discoveries = this.modules.discovery discoveries = Array.isArray(discoveries) ? discoveries : [discoveries] discoveries.forEach((discovery) => { - discovery.on('peer', (peerInfo) => { - this.discovery.emit('peer', peerInfo) - }) + discovery.on('peer', (peerInfo) => this.emit('peer', peerInfo)) }) } @@ -142,142 +137,42 @@ class Node { this.swarm.close(callback) } - // - // Ping - // - - // TODO - pingById (id, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) - callback(new Error('not implemented yet')) + isOn () { + return this.isOnline } - // TODO - pingByMultiaddr (maddr, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) - callback(new Error('not implemented yet')) - } - - pingByPeerInfo (peerInfo, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) + ping (peer, callback) { + assert(this.isOn(), OFFLINE_ERROR_MESSAGE) + const peerInfo = this._getPeerInfo(peer) callback(null, new Ping(this.swarm, peerInfo)) } - // - // Dialing methods - // - - // TODO - dialById (id, protocol, callback) { - // NOTE: dialById only works if a previous dial was made. This will - // change once we have PeerRouting - - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) - - if (typeof protocol === 'function') { - callback = protocol - protocol = undefined - } - - callback(new Error('not implemented yet')) - } - - dialByMultiaddr (maddr, protocol, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) - - if (typeof protocol === 'function') { - callback = protocol - protocol = undefined - } - - if (typeof maddr === 'string') { - maddr = multiaddr(maddr) - } - - if (!mafmt.IPFS.matches(maddr.toString())) { - return callback(new Error('multiaddr not valid')) - } - - const ipfsIdB58String = maddr.stringTuples().filter((tuple) => { - if (tuple[0] === IPFS_CODE) { - return true - } - })[0][1] - - let peer - try { - peer = this.peerBook.getByB58String(ipfsIdB58String) - } catch (err) { - peer = new PeerInfo(PeerId.createFromB58String(ipfsIdB58String)) - } - - peer.multiaddr.add(maddr) - this.dialByPeerInfo(peer, protocol, callback) - } - - dialByPeerInfo (peer, protocol, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) + dial (peer, protocol, callback) { + assert(this.isOn(), OFFLINE_ERROR_MESSAGE) + const peerInfo = this._getPeerInfo(peer) if (typeof protocol === 'function') { callback = protocol protocol = undefined } - this.swarm.dial(peer, protocol, (err, conn) => { + this.swarm.dial(peerInfo, protocol, (err, conn) => { if (err) { return callback(err) } - this.peerBook.put(peer) + this.peerBook.put(peerInfo) callback(null, conn) }) } - // - // Disconnecting (hangUp) methods - // + hangUp (peer, callback) { + assert(this.isOn(), OFFLINE_ERROR_MESSAGE) + const peerInfo = this._getPeerInfo(peer) - hangUpById (id, callback) { - // TODO - callback(new Error('not implemented yet')) + this.peerBook.removeByB58String(peerInfo.id.toB58String()) + this.swarm.hangUp(peerInfo, callback) } - hangUpByMultiaddr (maddr, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) - - if (typeof maddr === 'string') { - maddr = multiaddr(maddr) - } - - if (!mafmt.IPFS.matches(maddr.toString())) { - return callback(new Error('multiaddr not valid')) - } - - const ipfsIdB58String = maddr.stringTuples().filter((tuple) => { - if (tuple[0] === IPFS_CODE) { - return true - } - })[0][1] - - try { - const pi = this.peerBook.getByB58String(ipfsIdB58String) - this.hangUpByPeerInfo(pi, callback) - } catch (err) { - // already disconnected - callback() - } - } - - hangUpByPeerInfo (peer, callback) { - assert(this.isOnline, OFFLINE_ERROR_MESSAGE) - - this.peerBook.removeByB58String(peer.id.toB58String()) - this.swarm.hangUp(peer, callback) - } - - // - // Protocol multiplexing handling - // - handle (protocol, handlerFunc, matchFunc) { this.swarm.handle(protocol, handlerFunc, matchFunc) } @@ -285,6 +180,36 @@ class Node { unhandle (protocol) { this.swarm.unhandle(protocol) } + + /* + * Helper method to check the data type of peer and convert it to PeerInfo + */ + _getPeerInfo (peer) { + let p + if (PeerInfo.isPeerInfo(peer)) { + p = peer + } else if (multiaddr.isMultiaddr(peer)) { + const peerIdB58Str = peer.getPeerId() + try { + p = this.peerBook.getByB58String(peerIdB58Str) + } catch (err) { + p = new PeerInfo(PeerId.createFromB58String(peerIdB58Str)) + } + p.multiaddr.add(peer) + } else if (PeerId.isPeerId(peer)) { + const peerIdB58Str = peer.toB58String() + try { + p = this.peerBook.getByB58String(peerIdB58Str) + } catch (err) { + // TODO this is where PeerRouting comes into place + throw new Error('No knowledge about: ' + peerIdB58Str) + } + } else { + throw new Error('peer type not recognized') + } + + return p + } } module.exports = Node