zeronet-swarm
Version:
libp2p based ZeroNet swarm
145 lines (122 loc) • 3.99 kB
JavaScript
const debug = require('debug')
const log = debug('zeronet:swarm:zero:dial')
const multiaddr = require('multiaddr')
const uuid = require('uuid')
const crypto = require('crypto')
const sha5 = text => crypto.createHash('sha512').update(text).digest('hex')
const LimitDialer = require('libp2p-swarm/src/limit-dialer')
const Id = require('peer-id')
const Peer = require('peer-info')
const ip2multi = require('../lib/ip2multi')
function dialables (tp, multiaddrs) {
return tp.filter(multiaddrs)
}
function getMultiaddrList (pi) {
if (Peer.isPeerInfo(pi)) { return Peer.multiaddrs.toArray() }
if (multiaddr.isMultiaddr(pi)) {
return [pi]
}
if (ip2multi.isIp(pi)) {
return [multiaddr(ip2multi(pi, 'tcp'))]
}
if (typeof pi === 'string' && (pi.match(/\/.+\/.+\/.+\/.+\//) || pi.match(/\/.+\/.+\/.+\/.+/) || pi.match(/\/.+\/.+\/.+\/.+\/.+\/.+\//) || pi.match(/\/.+\/.+\/.+\/.+\/.+\/.+/))) {
return [multiaddr(pi)]
}
return []
}
function sortByTransport (tr, addrs) {
let r = []
for (var p in tr) {
const d = dialables(tr[p], addrs)
if (d.length) {
r.push({
transport: tr[p],
tr_name: p,
addrs: d
})
}
}
return r
}
module.exports = function ZeroSwarmDial (swarm, lp2p) {
const self = swarm
const tr = self.transport
const upgradeClient = self.proto.upgradeConn({
isServer: false
})
const conns = self.conns = {}
const dialer = new LimitDialer(8, 10 * 1000) // defaults from libp2p-swarm
self.connect = (peer, cb) => {
const addrs = getMultiaddrList(peer).slice(0)
if (!addrs.length) return cb(new Error('No addresses found in peerInfo'))
log('dialing %s address(es)', addrs.length)
const job = addrs.length === 1 ? sha5(sha5(addrs[0].toString())).substr(0, 10) : uuid()
const peerInfo = {
toB58String () {
return job
}
}
const jobs = sortByTransport(tr, addrs)
function finish (multiaddr, conn, cb) {
upgradeClient(conn, (err, client, upgradeable) => {
if (err) return cb(err)
if (!upgradeable) {
conn.client = client
client.ma = multiaddr
conns[multiaddr] = conn
return cb(null, client)
} else {
cb(null, null, upgradeable)
}
})
}
function tryDial (j, cb) {
dialer.dialMany(peerInfo, j.transport, j.addrs, (err, success) => {
if (err) return cb(err)
log('successfully dialed %s', success.multiaddr.toString())
return cb(null, success.multiaddr.toString(), success.conn)
})
}
function dialLoop () {
const j = jobs.shift()
if (!j) return cb(new Error("Couldn't dial into any of the addresses"))
log('dialing with %s (%s address(es))', j.tr_name, j.addrs.length)
tryDial(j, (err, multiaddr, conn) => {
if (err) return dialLoop()
return finish(multiaddr, conn, cb)
})
}
const readyconns = addrs.map(a => a.toString()).filter(a => conns[a]).map(a => conns[a])
if (!readyconns.length) dialLoop()
else {
if (Peer.isPeerInfo(readyconns[0])) cb(null, null, readyconns[0]) // upgrade is handled in .dial
else cb(null, readyconns[0].client)
}
}
self.dial = (peer, cmd, data, cb) => {
if (typeof cmd === 'function') {
cb = cmd
cmd = null
data = null
}
if (typeof data === 'function') {
cb = data
data = {}
}
self.connect(peer, (err, client, upgrade) => {
if (err) return cb(err)
if (upgrade) {
const id = upgrade._lp2p_id
const addr = lp2p.up.simulateDial(upgrade, id)
const pi = new Peer(Id.createFromB58String(id))
pi.multiaddrs.add(addr)
lp2p.cmd(pi, cmd, data, cb)
} else {
if (!cmd) return cb(null, client)
if (!client.cmd[cmd]) return cb(new Error('CMD Unsupported!')) // TODO: use a real method for that
client.cmd[cmd](data, cb)
}
})
}
}