2016-03-23 15:30:14 +01:00
|
|
|
'use strict'
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
const EventEmitter = require('events').EventEmitter
|
|
|
|
const assert = require('assert')
|
|
|
|
|
|
|
|
const setImmediate = require('async/setImmediate')
|
|
|
|
const each = require('async/each')
|
|
|
|
const series = require('async/series')
|
|
|
|
|
|
|
|
const Ping = require('libp2p-ping')
|
2016-11-26 03:07:52 +01:00
|
|
|
const Swarm = require('libp2p-swarm')
|
|
|
|
const PeerId = require('peer-id')
|
|
|
|
const PeerInfo = require('peer-info')
|
|
|
|
const PeerBook = require('peer-book')
|
2017-04-06 15:45:23 -04:00
|
|
|
const mafmt = require('mafmt')
|
2016-11-26 03:07:52 +01:00
|
|
|
const multiaddr = require('multiaddr')
|
2015-09-27 00:14:40 +01:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
exports = module.exports
|
2015-09-27 00:14:40 +01:00
|
|
|
|
2017-07-07 12:56:46 +01:00
|
|
|
const NOT_STARTED_ERROR_MESSAGE = 'The libp2p node is not started yet'
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2017-03-27 12:26:34 +01:00
|
|
|
class Node extends EventEmitter {
|
2016-12-11 12:03:21 -08:00
|
|
|
constructor (_modules, _peerInfo, _peerBook, _options) {
|
2017-03-27 12:26:34 +01:00
|
|
|
super()
|
2016-11-26 03:07:52 +01:00
|
|
|
assert(_modules, 'requires modules to equip libp2p with features')
|
|
|
|
assert(_peerInfo, 'requires a PeerInfo instance')
|
|
|
|
|
|
|
|
this.modules = _modules
|
|
|
|
this.peerInfo = _peerInfo
|
|
|
|
this.peerBook = _peerBook || new PeerBook()
|
2017-07-07 12:56:46 +01:00
|
|
|
this._isStarted = false
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2017-03-31 15:52:20 +01:00
|
|
|
this.swarm = new Swarm(this.peerInfo, this.peerBook)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
|
|
|
// Attach stream multiplexers
|
2017-07-06 14:26:20 +01:00
|
|
|
if (this.modules.connection && this.modules.connection.muxer) {
|
2016-11-26 03:07:52 +01:00
|
|
|
let muxers = this.modules.connection.muxer
|
|
|
|
muxers = Array.isArray(muxers) ? muxers : [muxers]
|
2017-04-06 15:45:23 -04:00
|
|
|
muxers.forEach((muxer) => this.swarm.connection.addStreamMuxer(muxer))
|
2016-11-26 03:07:52 +01:00
|
|
|
|
|
|
|
// If muxer exists, we can use Identify
|
|
|
|
this.swarm.connection.reuse()
|
|
|
|
|
2017-03-29 18:03:00 +01:00
|
|
|
// Received incommind dial and muxer upgrade happened,
|
|
|
|
// reuse this muxed connection
|
2016-11-26 03:07:52 +01:00
|
|
|
this.swarm.on('peer-mux-established', (peerInfo) => {
|
2017-03-29 07:32:46 +01:00
|
|
|
this.emit('peer:connect', peerInfo)
|
2016-11-26 03:07:52 +01:00
|
|
|
this.peerBook.put(peerInfo)
|
|
|
|
})
|
|
|
|
|
|
|
|
this.swarm.on('peer-mux-closed', (peerInfo) => {
|
2017-03-29 07:32:46 +01:00
|
|
|
this.emit('peer:disconnect', peerInfo)
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attach crypto channels
|
2017-07-06 14:26:20 +01:00
|
|
|
if (this.modules.connection && this.modules.connection.crypto) {
|
2016-11-26 03:07:52 +01:00
|
|
|
let cryptos = this.modules.connection.crypto
|
|
|
|
cryptos = Array.isArray(cryptos) ? cryptos : [cryptos]
|
|
|
|
cryptos.forEach((crypto) => {
|
|
|
|
this.swarm.connection.crypto(crypto.tag, crypto.encrypt)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attach discovery mechanisms
|
2017-01-28 20:59:47 +00:00
|
|
|
if (this.modules.discovery) {
|
2016-11-26 03:07:52 +01:00
|
|
|
let discoveries = this.modules.discovery
|
|
|
|
discoveries = Array.isArray(discoveries) ? discoveries : [discoveries]
|
2017-03-29 07:32:46 +01:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
discoveries.forEach((discovery) => {
|
2017-03-29 07:32:46 +01:00
|
|
|
discovery.on('peer', (peerInfo) => this.emit('peer:discovery', peerInfo))
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-12-01 11:53:27 +00:00
|
|
|
// Mount default protocols
|
|
|
|
Ping.mount(this.swarm)
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
// dht provided components (peerRouting, contentRouting, dht)
|
|
|
|
if (_modules.DHT) {
|
2017-07-20 10:20:10 -07:00
|
|
|
this._dht = new this.modules.DHT(this.swarm, {
|
|
|
|
kBucketSize: 20,
|
|
|
|
datastoer: _options.DHT && _options.DHT.datastore
|
|
|
|
})
|
2017-04-06 15:45:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
this.peerRouting = {
|
|
|
|
findPeer: (id, callback) => {
|
2017-04-16 16:54:31 +01:00
|
|
|
if (!this._dht) {
|
|
|
|
return callback(new Error('DHT is not available'))
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this._dht.findPeer(id, callback)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.contentRouting = {
|
|
|
|
findProviders: (key, timeout, callback) => {
|
2017-04-16 16:54:31 +01:00
|
|
|
if (!this._dht) {
|
|
|
|
return callback(new Error('DHT is not available'))
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this._dht.findProviders(key, timeout, callback)
|
|
|
|
},
|
|
|
|
provide: (key, callback) => {
|
2017-04-16 16:54:31 +01:00
|
|
|
if (!this._dht) {
|
|
|
|
return callback(new Error('DHT is not available'))
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this._dht.provide(key, callback)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.dht = {
|
|
|
|
put: (key, value, callback) => {
|
2017-04-16 16:54:31 +01:00
|
|
|
if (!this._dht) {
|
|
|
|
return callback(new Error('DHT is not available'))
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this._dht.put(key, value, callback)
|
|
|
|
},
|
|
|
|
get: (key, callback) => {
|
2017-04-16 16:54:31 +01:00
|
|
|
if (!this._dht) {
|
|
|
|
return callback(new Error('DHT is not available'))
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this._dht.get(key, callback)
|
|
|
|
},
|
|
|
|
getMany (key, nVals, callback) {
|
2017-04-16 16:54:31 +01:00
|
|
|
if (!this._dht) {
|
|
|
|
return callback(new Error('DHT is not available'))
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this._dht.getMany(key, nVals, callback)
|
|
|
|
}
|
|
|
|
}
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the libp2p node
|
|
|
|
* - create listeners on the multiaddrs the Peer wants to listen
|
|
|
|
*/
|
|
|
|
start (callback) {
|
|
|
|
if (!this.modules.transport) {
|
|
|
|
return callback(new Error('no transports were present'))
|
|
|
|
}
|
2016-11-27 18:20:25 +00:00
|
|
|
|
|
|
|
let ws
|
2016-11-26 03:07:52 +01:00
|
|
|
let transports = this.modules.transport
|
2016-11-27 18:20:25 +00:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
transports = Array.isArray(transports) ? transports : [transports]
|
2017-03-29 21:02:37 +01:00
|
|
|
|
|
|
|
// so that we can have webrtc-star addrs without adding manually the id
|
2017-03-31 15:52:20 +01:00
|
|
|
const maOld = []
|
|
|
|
const maNew = []
|
|
|
|
this.peerInfo.multiaddrs.forEach((ma) => {
|
2017-03-29 21:02:37 +01:00
|
|
|
if (!mafmt.IPFS.matches(ma)) {
|
2017-03-31 15:52:20 +01:00
|
|
|
maOld.push(ma)
|
|
|
|
maNew.push(ma.encapsulate('/ipfs/' + this.peerInfo.id.toB58String()))
|
2017-03-29 21:02:37 +01:00
|
|
|
}
|
|
|
|
})
|
2017-03-31 15:52:20 +01:00
|
|
|
this.peerInfo.multiaddrs.replace(maOld, maNew)
|
|
|
|
const multiaddrs = this.peerInfo.multiaddrs.toArray()
|
2016-11-27 18:20:25 +00:00
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
transports.forEach((transport) => {
|
|
|
|
if (transport.filter(multiaddrs).length > 0) {
|
|
|
|
this.swarm.transport.add(
|
|
|
|
transport.tag || transport.constructor.name, transport)
|
2016-11-27 18:20:25 +00:00
|
|
|
} else if (transport.constructor &&
|
|
|
|
transport.constructor.name === 'WebSockets') {
|
|
|
|
// TODO find a cleaner way to signal that a transport is always
|
|
|
|
// used for dialing, even if no listener
|
|
|
|
ws = transport
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
series([
|
|
|
|
(cb) => this.swarm.listen(cb),
|
|
|
|
(cb) => {
|
|
|
|
if (ws) {
|
|
|
|
// always add dialing on websockets
|
|
|
|
this.swarm.transport.add(ws.tag || ws.constructor.name, ws)
|
|
|
|
}
|
|
|
|
|
|
|
|
// all transports need to be setup before discover starts
|
|
|
|
if (this.modules.discovery) {
|
|
|
|
return each(this.modules.discovery, (d, cb) => d.start(cb), cb)
|
|
|
|
}
|
|
|
|
cb()
|
|
|
|
},
|
|
|
|
(cb) => {
|
2017-07-07 12:56:46 +01:00
|
|
|
// TODO: chicken-and-egg problem:
|
|
|
|
// have to set started here because DHT requires libp2p is already started
|
|
|
|
this._isStarted = true
|
2017-04-06 15:45:23 -04:00
|
|
|
if (this._dht) {
|
|
|
|
return this._dht.start(cb)
|
|
|
|
}
|
|
|
|
cb()
|
2017-07-07 12:56:46 +01:00
|
|
|
},
|
|
|
|
(cb) => {
|
|
|
|
this.emit('start')
|
|
|
|
cb()
|
2016-11-27 18:20:25 +00:00
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
], callback)
|
2015-09-27 00:14:40 +01:00
|
|
|
}
|
|
|
|
|
2016-11-26 03:07:52 +01:00
|
|
|
/*
|
|
|
|
* Stop the libp2p node by closing its listeners and open connections
|
|
|
|
*/
|
|
|
|
stop (callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
this._isStarted = false
|
2017-01-28 20:59:47 +00:00
|
|
|
|
|
|
|
if (this.modules.discovery) {
|
|
|
|
this.modules.discovery.forEach((discovery) => {
|
|
|
|
setImmediate(() => discovery.stop(() => {}))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
series([
|
|
|
|
(cb) => {
|
|
|
|
if (this._dht) {
|
|
|
|
return this._dht.stop(cb)
|
|
|
|
}
|
|
|
|
cb()
|
|
|
|
},
|
2017-07-07 12:56:46 +01:00
|
|
|
(cb) => this.swarm.close(cb),
|
|
|
|
(cb) => {
|
|
|
|
this.emit('stop')
|
|
|
|
cb()
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
], callback)
|
2015-09-27 00:14:40 +01:00
|
|
|
}
|
|
|
|
|
2017-07-07 12:56:46 +01:00
|
|
|
isStarted () {
|
|
|
|
return this._isStarted
|
2016-12-01 11:53:27 +00:00
|
|
|
}
|
|
|
|
|
2017-03-27 12:26:34 +01:00
|
|
|
ping (peer, callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE)
|
2017-04-06 15:45:23 -04:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, new Ping(this.swarm, peerInfo))
|
|
|
|
})
|
2016-12-01 11:53:27 +00:00
|
|
|
}
|
|
|
|
|
2017-03-27 12:26:34 +01:00
|
|
|
dial (peer, protocol, callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
|
|
|
if (typeof protocol === 'function') {
|
|
|
|
callback = protocol
|
|
|
|
protocol = undefined
|
|
|
|
}
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
2016-11-26 03:07:52 +01:00
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2017-04-06 15:45:23 -04:00
|
|
|
|
|
|
|
this.swarm.dial(peerInfo, protocol, (err, conn) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
this.peerBook.put(peerInfo)
|
|
|
|
callback(null, conn)
|
|
|
|
})
|
2016-11-26 03:07:52 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-03-27 12:26:34 +01:00
|
|
|
hangUp (peer, callback) {
|
2017-07-07 12:56:46 +01:00
|
|
|
assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE)
|
2016-11-26 03:07:52 +01:00
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
this._getPeerInfo(peer, (err, peerInfo) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.swarm.hangUp(peerInfo, callback)
|
|
|
|
})
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
handle (protocol, handlerFunc, matchFunc) {
|
|
|
|
this.swarm.handle(protocol, handlerFunc, matchFunc)
|
|
|
|
}
|
|
|
|
|
|
|
|
unhandle (protocol) {
|
|
|
|
this.swarm.unhandle(protocol)
|
|
|
|
}
|
2017-03-27 12:26:34 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Helper method to check the data type of peer and convert it to PeerInfo
|
|
|
|
*/
|
2017-04-06 15:45:23 -04:00
|
|
|
_getPeerInfo (peer, callback) {
|
2017-03-27 12:26:34 +01:00
|
|
|
let p
|
2017-04-06 15:45:23 -04:00
|
|
|
// PeerInfo
|
2017-03-27 14:42:05 +01:00
|
|
|
if (PeerInfo.isPeerInfo(peer)) {
|
|
|
|
p = peer
|
2017-04-06 15:45:23 -04:00
|
|
|
// Multiaddr instance (not string)
|
2017-03-27 14:42:05 +01:00
|
|
|
} else if (multiaddr.isMultiaddr(peer)) {
|
|
|
|
const peerIdB58Str = peer.getPeerId()
|
|
|
|
try {
|
2017-03-31 16:17:59 +01:00
|
|
|
p = this.peerBook.get(peerIdB58Str)
|
2017-03-27 14:42:05 +01:00
|
|
|
} catch (err) {
|
|
|
|
p = new PeerInfo(PeerId.createFromB58String(peerIdB58Str))
|
|
|
|
}
|
2017-03-31 15:52:20 +01:00
|
|
|
p.multiaddrs.add(peer)
|
2017-04-06 15:45:23 -04:00
|
|
|
// PeerId
|
2017-03-27 14:42:05 +01:00
|
|
|
} else if (PeerId.isPeerId(peer)) {
|
|
|
|
const peerIdB58Str = peer.toB58String()
|
|
|
|
try {
|
2017-03-31 16:17:59 +01:00
|
|
|
p = this.peerBook.get(peerIdB58Str)
|
2017-03-27 14:42:05 +01:00
|
|
|
} catch (err) {
|
2017-04-06 15:45:23 -04:00
|
|
|
return this.peerRouting.findPeer(peer, callback)
|
2017-03-27 14:42:05 +01:00
|
|
|
}
|
|
|
|
} else {
|
2017-04-06 15:45:23 -04:00
|
|
|
return setImmediate(() => callback(new Error('peer type not recognized')))
|
2017-03-27 12:26:34 +01:00
|
|
|
}
|
|
|
|
|
2017-04-06 15:45:23 -04:00
|
|
|
setImmediate(() => callback(null, p))
|
2017-03-27 12:26:34 +01:00
|
|
|
}
|
2016-11-26 03:07:52 +01:00
|
|
|
}
|
|
|
|
|
2016-11-28 10:38:40 +00:00
|
|
|
module.exports = Node
|