Merge pull request #64 from libp2p/feat/new-api

feat: Formalising the API
This commit is contained in:
David Dias 2017-03-27 15:45:41 +01:00 committed by GitHub
commit 721da9ab41
3 changed files with 187 additions and 138 deletions

141
README.md
View File

@ -56,24 +56,149 @@ 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:**
Defined by [interface-libp2p](https://github.com/libp2p/interface-libp2p)
```JavaScript
// Creating a bundle that adds:
// transport: websockets + tcp
// stream-muxing: SPDY
// crypto-channel: secio
// discovery: multicast-dns
## Development
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')
## Linting
class Node extends libp2p {
constructor (peerInfo, peerBook, options) {
options = options || {}
```sh
> npm run lint
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.
```
### API
#### Create a Node - `new libp2p.Node([peerInfo, peerBook, options])`
> 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
### Packages
List of packages currently in existence for libp2p

View File

@ -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 <daviddias.p@gmail.com>",
@ -55,4 +54,4 @@
"greenkeeperio-bot <support@greenkeeper.io>",
"mayerwin <mayerwin@users.noreply.github.com>"
]
}
}

View File

@ -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)
dial (peer, protocol, callback) {
assert(this.isOn(), OFFLINE_ERROR_MESSAGE)
const peerInfo = this._getPeerInfo(peer)
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)
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