diff --git a/.aegir.js b/.aegir.js index f8c90e7..c1a72e9 100644 --- a/.aegir.js +++ b/.aegir.js @@ -6,7 +6,9 @@ const server = createServer() module.exports = { hooks: { - pre: server.start.bind(server), - post: server.stop.bind(server) + browser: { + pre: () => server.start(), + post: () => server.stop() + } } } diff --git a/package.json b/package.json index 77d0bee..f3a4778 100644 --- a/package.json +++ b/package.json @@ -18,18 +18,19 @@ "coverage": "aegir coverage" }, "devDependencies": { - "aegir": "^15.2.0", + "aegir": "^19.0.5", + "async-iterator-all": "^1.0.0", "chai": "^4.2.0", - "cids": "~0.5.5", + "cids": "^0.7.1", "go-ipfs-dep": "~0.4.17", - "ipfsd-ctl": "~0.39.2" + "ipfsd-ctl": "~0.44.0" }, "dependencies": { - "async": "^2.6.1", - "ipfs-api": "^24.0.2", - "multiaddr": "^5.0.0", - "peer-id": "~0.11.0", - "peer-info": "~0.14.1" + "ipfs": "^0.36.4", + "ipfs-http-client": "^32.0.1", + "multiaddr": "^6.1.0", + "peer-id": "^0.12.2", + "peer-info": "^0.15.1" }, "contributors": [ "David Dias ", diff --git a/src/index.js b/src/index.js index 6ba6feb..8afffae 100644 --- a/src/index.js +++ b/src/index.js @@ -2,13 +2,10 @@ const PeerInfo = require('peer-info') const PeerID = require('peer-id') -const dht = require('ipfs-api/src/dht') -const swarm = require('ipfs-api/src/swarm') -const refs = require('ipfs-api/src/refs') -const defaultConfig = require('ipfs-api/src/utils/default-config') -const series = require('async/series') -const parallel = require('async/parallel') -const reflect = require('async/reflect') +const dht = require('ipfs-http-client/src/dht') +const swarm = require('ipfs-http-client/src/swarm') +const refs = require('ipfs-http-client/src/files-regular/refs') +const defaultConfig = require('ipfs-http-client/src/utils/default-config') const multiaddr = require('multiaddr') const DEFAULT_MAX_TIMEOUT = 30e3 // 30 second default @@ -62,48 +59,18 @@ class DelegatedContentRouting { * @param {CID} key * @param {object} options * @param {number} options.maxTimeout How long the query can take. Defaults to 30 seconds - * @param {function(Error, Array)} callback - * @returns {void} + * @returns {AsyncIterable} */ - findProviders (key, options, callback) { - if (typeof options === 'function') { - callback = options - options = {} - } else if (typeof options === 'number') { // This will be deprecated in a next release - options = { - maxTimeout: options - } - } else { - options = options || {} - } - + async * findProviders (key, options = {}) { options.maxTimeout = options.maxTimeout || DEFAULT_MAX_TIMEOUT - this.dht.findprovs(key.toBaseEncodedString(), { + const results = await this.dht.findProvs(key.toBaseEncodedString(), { timeout: `${options.maxTimeout}ms` // The api requires specification of the time unit (s/ms) - }, (err, results) => { - if (err) { - return callback(err) - } - - // cleanup result from ipfs-api - const infos = [] - results - .filter((res) => Boolean(res.Responses)) - .forEach((res) => { - res.Responses.forEach((raw) => { - const info = new PeerInfo( - PeerID.createFromB58String(raw.ID) - ) - if (raw.Addrs) { - raw.Addrs.forEach((addr) => info.multiaddrs.add(addr)) - } - infos.push(info) - }) - }) - - callback(null, infos) }) + + for (let i = 0; i < results.length; i++) { + yield results[i] + } } /** @@ -115,32 +82,29 @@ class DelegatedContentRouting { * * @param {CID} key * @param {function(Error)} callback - * @returns {void} + * @returns {Promise} */ - provide (key, callback) { + async provide (key) { const addrs = this.bootstrappers.map((addr) => { return addr.encapsulate(`/p2p-circuit/ipfs/${this.peerId.toB58String()}`) }) - series([ - (cb) => parallel(addrs.map((addr) => { - return reflect((cb) => this.swarm.connect(addr.toString(), cb)) - }), (err, results) => { - if (err) { - return cb(err) - } + const results = await Promise.all( + addrs.map((addr) => { + return this.swarm.connect(addr.toString()).catch(() => {}) + }) + ) + + // only some need to succeed + const success = results.filter((res) => res && res.error == null) - // only some need to succeed - const success = results.filter((res) => res.error == null) - if (success.length === 0) { - return cb(new Error('unable to swarm.connect using p2p-circuit')) - } - cb() - }), - (cb) => { - this.refs(key.toBaseEncodedString(), {recursive: true}, cb) - } - ], (err) => callback(err)) + if (success.length === 0) { + throw new Error('unable to swarm.connect using p2p-circuit') + } + + return this.refs(key.toBaseEncodedString(), { + recursive: true + }) } } diff --git a/test/index.spec.js b/test/index.spec.js index e4a70dd..a9f23c3 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -3,39 +3,34 @@ const expect = require('chai').expect const IPFSFactory = require('ipfsd-ctl') -const async = require('async') const CID = require('cids') const PeerId = require('peer-id') - -const factory = IPFSFactory.create({ type: 'go' }) +const all = require('async-iterator-all') +const factory = IPFSFactory.create({ + type: 'go' +}) const DelegatedContentRouting = require('../src') -function spawnNode (boostrap, callback) { - if (typeof boostrap === 'function') { - callback = boostrap - boostrap = [] - } - - factory.spawn({ +async function spawnNode (bootstrap = []) { + const node = await factory.spawn({ // Lock down the nodes so testing can be deterministic config: { - Bootstrap: boostrap, + Bootstrap: bootstrap, Discovery: { MDNS: { Enabled: false } } } - }, (err, node) => { - if (err) return callback(err) + }) - node.api.id((err, id) => { - if (err) return callback(err) + const id = await node.api.id() - callback(null, node, id) - }) - }) + return { + node, + id + } } describe('DelegatedContentRouting', function () { @@ -43,41 +38,38 @@ describe('DelegatedContentRouting', function () { let selfNode let selfId - let delegatedNode + let delegateNode let bootstrapNode let bootstrapId - - before((done) => { - async.waterfall([ - // Spawn a "Boostrap" node that doesnt connect to anything - (cb) => spawnNode(cb), - (ipfsd, id, cb) => { - bootstrapNode = ipfsd - bootstrapId = id - cb() - }, - // Spawn our local node and bootstrap the bootstrapper node - (cb) => spawnNode(bootstrapId.addresses, cb), - (ipfsd, id, cb) => { - selfNode = ipfsd - selfId = PeerId.createFromB58String(id.id) - cb() - }, - // Spawn the delegate node and bootstrap the bootstrapper node - (cb) => spawnNode(bootstrapId.addresses, cb), - (ipfsd, id, cb) => { - delegatedNode = ipfsd - cb() - } - ], done) + let cid + + before(async () => { + // Spawn a "Boostrap" node that doesnt connect to anything + const bootstrap = await spawnNode() + bootstrapNode = bootstrap.node + bootstrapId = bootstrap.id + + // Spawn our local node and bootstrap the bootstrapper node + const self = await spawnNode(bootstrapId.addresses) + selfNode = self.node + selfId = PeerId.createFromB58String(self.id.id) + + // Spawn the delegate node and bootstrap the bootstrapper node + const delegate = await spawnNode(bootstrapId.addresses) + delegateNode = delegate.node + + // Add a piece of content to the bootstrap node + const content = Buffer.from(`hello-${Math.random()}`) + const res = await bootstrapNode.api.add(content) + cid = new CID(res[0].hash) }) - after((done) => { - async.parallel([ - (cb) => selfNode.stop(cb), - (cb) => delegatedNode.stop(cb), - (cb) => bootstrapNode.stop(cb) - ], done) + after(async () => { + return Promise.all([ + selfNode.stop(), + delegateNode.stop(), + bootstrapNode.stop() + ]) }) describe('create', () => { @@ -123,93 +115,56 @@ describe('DelegatedContentRouting', function () { }) describe('findProviders', () => { - it('should be able to find providers through the delegate node', function (done) { - async.waterfall([ - (cb) => { - const opts = delegatedNode.apiAddr.toOptions() - const routing = new DelegatedContentRouting(selfId, { - protocol: 'http', - port: opts.port, - host: opts.host - }) - const cid = new CID('QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv') - routing.findProviders(cid, cb) - }, - (providers, cb) => { - // We should get our local node and the bootstrap node as providers. - // The delegate node is not included, because it is handling the requests - expect(providers).to.have.length(2) - expect(providers.map((p) => p.id.toB58String())).to.have.members([ - bootstrapId.id, - selfId.toB58String() - ]) - cb() - } - ], done) + it('should be able to find providers through the delegate node', async () => { + const opts = delegateNode.apiAddr.toOptions() + const routing = new DelegatedContentRouting(selfId, { + protocol: 'http', + port: opts.port, + host: opts.host + }) + + const providers = await all(routing.findProviders(cid)) + + // We should get the bootstrap node as provider + // The delegate node is not included, because it is handling the requests + expect(providers.map((p) => p.id.toB58String())).to.include(bootstrapId.id, 'Did not include bootstrap node') + expect(providers.map((p) => p.id.toB58String())).to.include(selfId.toB58String(), 'Did not include self node') }) - it('should be able to specify a maxTimeout', function (done) { - async.waterfall([ - (cb) => { - const opts = delegatedNode.apiAddr.toOptions() - const routing = new DelegatedContentRouting(selfId, { - protocol: 'http', - port: opts.port, - host: opts.host - }) - const cid = new CID('QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv') - routing.findProviders(cid, { maxTimeout: 5e3 }, cb) - }, - (providers, cb) => { - // We should get our local node and the bootstrap node as providers. - // The delegate node is not included, because it is handling the requests - expect(providers).to.have.length(2) - expect(providers.map((p) => p.id.toB58String())).to.have.members([ - bootstrapId.id, - selfId.toB58String() - ]) - cb() - } - ], done) + it('should be able to specify a maxTimeout', async () => { + const opts = delegateNode.apiAddr.toOptions() + const routing = new DelegatedContentRouting(selfId, { + protocol: 'http', + port: opts.port, + host: opts.host + }) + + const cid = new CID('QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv') + const providers = await all(routing.findProviders(cid, { maxTimeout: 5e3 })) + + expect(providers.map((p) => p.id.toB58String())).to.include(bootstrapId.id, 'Did not include bootstrap node') }) }) describe('provide', () => { - it('should be able to register as a content provider to the delegate node', function (done) { + it('should be able to register as a content provider to the delegate node', async () => { let contentRouter let cid - async.waterfall([ - (cb) => { - const opts = delegatedNode.apiAddr.toOptions() - contentRouter = new DelegatedContentRouting(selfId, { - protocol: 'http', - port: opts.port, - host: opts.host - }) - - selfNode.api.files.add(Buffer.from(`hello-${Math.random()}`), cb) - }, - (res, cb) => { - cid = new CID(res[0].hash) - contentRouter.provide(cid, cb) - }, - (cb) => { - delegatedNode.api.dht.findprovs(cid.toBaseEncodedString(), cb) - }, - (provs, cb) => { - let providers = [] - provs.filter((res) => Boolean(res.Responses)).forEach((res) => { - providers = providers.concat(res.Responses) - }) - - // We are hosting the file, validate we're the provider - const res = providers.find((prov) => prov.ID === selfId.toB58String()) - expect(res.ID).to.equal(selfId.toB58String()) - - cb() - } - ], done) + const opts = delegateNode.apiAddr.toOptions() + contentRouter = new DelegatedContentRouting(selfId, { + protocol: 'http', + port: opts.port, + host: opts.host + }) + + const res = await selfNode.api.add(Buffer.from(`hello-${Math.random()}`)) + cid = new CID(res[0].hash) + await contentRouter.provide(cid) + const providers = await delegateNode.api.dht.findProvs(cid.toBaseEncodedString()) + + // We are hosting the file, validate we're the provider + expect(providers.map((p) => p.id.toB58String())).to.include(selfId.toB58String(), 'Did not include self node') }) }) })