From e33befc5cdcce57832a673ecd3edc62c18440f4f Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 19 Jul 2018 22:33:47 +0100 Subject: [PATCH 1/9] feat: add partial implementation for ipfs.resolve License: MIT Signed-off-by: Alan Shaw --- src/core/components/index.js | 1 + src/core/components/resolve.js | 86 ++++++++++++++++++++++++++++++++++ src/core/index.js | 1 + test/core/interface.spec.js | 8 +++- 4 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/core/components/resolve.js diff --git a/src/core/components/index.js b/src/core/components/index.js index 1f6f084dee..6fc833499d 100644 --- a/src/core/components/index.js +++ b/src/core/components/index.js @@ -27,3 +27,4 @@ exports.dns = require('./dns') exports.key = require('./key') exports.stats = require('./stats') exports.mfs = require('./mfs') +exports.resolve = require('./resolve') diff --git a/src/core/components/resolve.js b/src/core/components/resolve.js new file mode 100644 index 0000000000..dd866cbfbb --- /dev/null +++ b/src/core/components/resolve.js @@ -0,0 +1,86 @@ +'use strict' + +const promisify = require('promisify-es6') +const isIpfs = require('is-ipfs') +const setImmediate = require('async/setImmediate') +const doUntil = require('async/doUntil') +const CID = require('cids') + +module.exports = (self) => { + return promisify((name, opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = opts || {} + + if (!isIpfs.path(name)) { + return setImmediate(() => cb(new Error('invalid argument'))) + } + + // TODO remove this and update subsequent code when IPNS is implemented + if (!isIpfs.ipfsPath(name)) { + return setImmediate(() => cb(new Error('resolve non-IPFS names is not implemented'))) + } + + const split = name.split('/') // ['', 'ipfs', 'hash', ...path] + const cid = new CID(split[2]) + + if (split.length === 3) { + return setImmediate(() => cb(null, name)) + } + + const path = split.slice(3).join('/') + + resolve(cid, path, (err, cid) => { + if (err) return cb(err) + if (!cid) return cb(new Error('found non-link at given path')) + cb(null, `/ipfs/${cid.toBaseEncodedString(opts.cidBase)}`) + }) + }) + + // Resolve the given CID + path to a CID. + function resolve (cid, path, callback) { + let value + + doUntil( + (cb) => { + self.block.get(cid, (err, block) => { + if (err) return cb(err) + + const r = self._ipld.resolvers[cid.codec] + + if (!r) { + return cb(new Error(`No resolver found for codec "${cid.codec}"`)) + } + + r.resolver.resolve(block.data, path, (err, result) => { + if (err) return cb(err) + value = result.value + path = result.remainderPath + cb() + }) + }) + }, + () => { + const endReached = !path || path === '/' + + if (endReached) { + return true + } + + if (value) { + cid = new CID(value['/']) + } + + return false + }, + (err) => { + if (err) return callback(err) + if (value && value['/']) return callback(null, new CID(value['/'])) + callback() + } + ) + } +} diff --git a/src/core/index.js b/src/core/index.js index 52266b7b41..f9b29c5145 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -120,6 +120,7 @@ class IPFS extends EventEmitter { this.dns = components.dns(this) this.key = components.key(this) this.stats = components.stats(this) + this.resolve = components.resolve(this) if (this._options.EXPERIMENTAL.pubsub) { this.log('EXPERIMENTAL pubsub is enabled') diff --git a/test/core/interface.spec.js b/test/core/interface.spec.js index 44ccf730dd..8ac1655b97 100644 --- a/test/core/interface.spec.js +++ b/test/core/interface.spec.js @@ -55,8 +55,12 @@ describe('interface-ipfs-core tests', () => { }), { skip: [ { - name: 'resolve', - reason: 'TODO: not implemented' + name: 'should resolve an IPNS DNS link', + reason: 'TODO IPNS not implemented yet' + }, + { + name: 'should resolve IPNS link recursively', + reason: 'TODO IPNS not implemented yet' } ] }) From 3df531800da8ae63ff4ea4de4005fcb6124dba9f Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 20 Jul 2018 16:14:01 +0100 Subject: [PATCH 2/9] feat: WIP add ipfs.resolve CLI and HTTP command License: MIT Signed-off-by: Alan Shaw --- src/cli/commands/resolve.js | 24 ++++++++++++++++++++ src/http/api/resources/index.js | 1 + src/http/api/resources/resolve.js | 37 +++++++++++++++++++++++++++++++ src/http/api/routes/resolve.js | 16 +++++++++++++ test/cli/resolve.js | 32 ++++++++++++++++++++++++++ 5 files changed, 110 insertions(+) create mode 100644 src/cli/commands/resolve.js create mode 100644 src/http/api/resources/resolve.js create mode 100644 src/http/api/routes/resolve.js create mode 100644 test/cli/resolve.js diff --git a/src/cli/commands/resolve.js b/src/cli/commands/resolve.js new file mode 100644 index 0000000000..c1b7a54991 --- /dev/null +++ b/src/cli/commands/resolve.js @@ -0,0 +1,24 @@ +'use strict' + +const print = require('../utils').print + +module.exports = { + command: 'resolve ', + + description: 'Resolve the value of names to IPFS', + + builder: { + recursive: { + alias: 'r', + type: 'boolean', + default: false + } + }, + + handler (argv) { + argv.ipfs.resolve(argv.name, { recursive: argv.recursive }, (err, res) => { + if (err) throw err + print(res) + }) + } +} diff --git a/src/http/api/resources/index.js b/src/http/api/resources/index.js index 59040a99d8..f937bf2e1b 100644 --- a/src/http/api/resources/index.js +++ b/src/http/api/resources/index.js @@ -18,3 +18,4 @@ exports.pubsub = require('./pubsub') exports.dns = require('./dns') exports.key = require('./key') exports.stats = require('./stats') +exports.resolve = require('./resolve') diff --git a/src/http/api/resources/resolve.js b/src/http/api/resources/resolve.js new file mode 100644 index 0000000000..417f50d8e8 --- /dev/null +++ b/src/http/api/resources/resolve.js @@ -0,0 +1,37 @@ +'use strict' + +const Joi = require('joi') +const debug = require('debug') + +const log = debug('jsipfs:http-api:resolve') +log.error = debug('jsipfs:http-api:resolve:error') + +module.exports = { + validate: { + query: Joi.object().keys({ + r: Joi.alternatives() + .when('recursive', { + is: Joi.any().exist(), + then: Joi.any().forbidden(), + otherwise: Joi.boolean() + }), + recursive: Joi.boolean(), + arg: Joi.string().required() + }).unknown() + }, + handler (request, reply) { + const ipfs = request.server.app.ipfs + const name = request.query.arg + const recursive = request.query.r || request.query.recursive || false + + log(name, { recursive }) + + ipfs.resolve(name, { recursive }, (err, res) => { + if (err) { + log.error(err) + return reply({ Message: err.message, Code: 0 }).code(500) + } + reply({ Path: res }) + }) + } +} diff --git a/src/http/api/routes/resolve.js b/src/http/api/routes/resolve.js new file mode 100644 index 0000000000..259ae3bdd1 --- /dev/null +++ b/src/http/api/routes/resolve.js @@ -0,0 +1,16 @@ +'use strict' + +const resources = require('./../resources') + +module.exports = (server) => { + const api = server.select('API') + + api.route({ + method: '*', + path: '/api/v0/resolve', + config: { + handler: resources.resolve.handler, + validate: resources.resolve.validate + } + }) +} diff --git a/test/cli/resolve.js b/test/cli/resolve.js new file mode 100644 index 0000000000..168c0aafc7 --- /dev/null +++ b/test/cli/resolve.js @@ -0,0 +1,32 @@ +/* eslint-env mocha */ +'use strict' + +const Path = require('path') +const expect = require('chai').expect +const isIpfs = require('is-ipfs') + +const runOnAndOff = require('../utils/on-and-off') + +describe('resolve', () => runOnAndOff((thing) => { + let ipfs + + before(() => { + ipfs = thing.ipfs + }) + + it('should resolve an IPFS hash', (done) => { + const path = '/src/init-files/init-docs/readme' + let hash + + return ipfs(`add ${Path.join(process.cwd(), path)}`) + .then((out) => { + hash = out.split(' ')[1] + console.log(out) + expect(isIpfs.cid(hash)).to.be.true() + return ipfs(`resolve ${hash}`) + }) + .then((out) => { + expect(out).to.contain(`/ipfs/${hash}`) + }) + }) +})) From b4419ea3126b2fdc0715d4ca780907056e5cb3aa Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 23 Jul 2018 11:40:33 +0100 Subject: [PATCH 3/9] test: add resolve IPFS path test License: MIT Signed-off-by: Alan Shaw --- src/http/api/routes/index.js | 1 + test/cli/resolve.js | 32 ++++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/http/api/routes/index.js b/src/http/api/routes/index.js index bfec26a460..4087299ecd 100644 --- a/src/http/api/routes/index.js +++ b/src/http/api/routes/index.js @@ -21,4 +21,5 @@ module.exports = (server) => { require('./dns')(server) require('./key')(server) require('./stats')(server) + require('./resolve')(server) } diff --git a/test/cli/resolve.js b/test/cli/resolve.js index 168c0aafc7..667c459e3b 100644 --- a/test/cli/resolve.js +++ b/test/cli/resolve.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ 'use strict' -const Path = require('path') +const path = require('path') const expect = require('chai').expect const isIpfs = require('is-ipfs') @@ -14,19 +14,39 @@ describe('resolve', () => runOnAndOff((thing) => { ipfs = thing.ipfs }) - it('should resolve an IPFS hash', (done) => { - const path = '/src/init-files/init-docs/readme' + it('should resolve an IPFS hash', () => { + const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme') let hash - return ipfs(`add ${Path.join(process.cwd(), path)}`) + return ipfs(`add ${filePath}`) .then((out) => { hash = out.split(' ')[1] - console.log(out) expect(isIpfs.cid(hash)).to.be.true() - return ipfs(`resolve ${hash}`) + return ipfs(`resolve /ipfs/${hash}`) }) .then((out) => { expect(out).to.contain(`/ipfs/${hash}`) }) }) + + it('should resolve an IPFS path link', () => { + const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme') + let fileHash, rootHash + + return ipfs(`add ${filePath} --wrap-with-directory`) + .then((out) => { + const lines = out.split('\n') + + fileHash = lines[0].split(' ')[1] + rootHash = lines[1].split(' ')[1] + + expect(isIpfs.cid(fileHash)).to.be.true() + expect(isIpfs.cid(rootHash)).to.be.true() + + return ipfs(`resolve /ipfs/${rootHash}/readme`) + }) + .then((out) => { + expect(out).to.contain(`/ipfs/${fileHash}`) + }) + }) })) From 0b5db1a748df79574423dcf3542064e5d0d67573 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 23 Jul 2018 12:31:47 +0100 Subject: [PATCH 4/9] fix: command count for tests License: MIT Signed-off-by: Alan Shaw --- test/cli/commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/commands.js b/test/cli/commands.js index 2c0c5fc033..2c52e23d74 100644 --- a/test/cli/commands.js +++ b/test/cli/commands.js @@ -4,7 +4,7 @@ const expect = require('chai').expect const runOnAndOff = require('../utils/on-and-off') -const commandCount = 77 +const commandCount = 78 describe('commands', () => runOnAndOff((thing) => { let ipfs From 762ac482bfe185268363d1fef693701439831272 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 23 Jul 2018 12:32:12 +0100 Subject: [PATCH 5/9] fix: skip tests that do not pass on js-ipfs yet License: MIT Signed-off-by: Alan Shaw --- test/http-api/interface.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/http-api/interface.js b/test/http-api/interface.js index b27c2b22fb..ac33dfb370 100644 --- a/test/http-api/interface.js +++ b/test/http-api/interface.js @@ -40,8 +40,12 @@ describe('interface-ipfs-core over ipfs-api tests', () => { }), { skip: [ { - name: 'resolve', - reason: 'TODO: not implemented' + name: 'should resolve an IPNS DNS link', + reason: 'TODO IPNS not implemented yet' + }, + { + name: 'should resolve IPNS link recursively', + reason: 'TODO IPNS not implemented yet' } ] }) From a7984d15aca1817c5707a35dcc20fdcb44cf60e2 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 23 Jul 2018 12:42:20 +0100 Subject: [PATCH 6/9] fix: increase timeout for resolve tests License: MIT Signed-off-by: Alan Shaw --- test/cli/resolve.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/cli/resolve.js b/test/cli/resolve.js index 667c459e3b..ebbd12b5ff 100644 --- a/test/cli/resolve.js +++ b/test/cli/resolve.js @@ -7,14 +7,16 @@ const isIpfs = require('is-ipfs') const runOnAndOff = require('../utils/on-and-off') -describe('resolve', () => runOnAndOff((thing) => { +describe.only('resolve', () => runOnAndOff((thing) => { let ipfs before(() => { ipfs = thing.ipfs }) - it('should resolve an IPFS hash', () => { + it('should resolve an IPFS hash', function () { + this.timeout(10 * 1000) + const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme') let hash @@ -29,7 +31,9 @@ describe('resolve', () => runOnAndOff((thing) => { }) }) - it('should resolve an IPFS path link', () => { + it('should resolve an IPFS path link', function () { + this.timeout(10 * 1000) + const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme') let fileHash, rootHash From 90dac4edde305524a5e40fe544b78b9c09054ab2 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 23 Jul 2018 12:44:11 +0100 Subject: [PATCH 7/9] fix: remove .only License: MIT Signed-off-by: Alan Shaw --- test/cli/resolve.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/resolve.js b/test/cli/resolve.js index ebbd12b5ff..fe670d4d15 100644 --- a/test/cli/resolve.js +++ b/test/cli/resolve.js @@ -7,7 +7,7 @@ const isIpfs = require('is-ipfs') const runOnAndOff = require('../utils/on-and-off') -describe.only('resolve', () => runOnAndOff((thing) => { +describe('resolve', () => runOnAndOff((thing) => { let ipfs before(() => { From 48cb184aa41cb672cae68d2095708584e70e51fc Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 6 Aug 2018 22:14:11 +0100 Subject: [PATCH 8/9] chore: update ipfs-api dependency License: MIT Signed-off-by: Alan Shaw --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3cd1773ac3..febaa37653 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "hoek": "^5.0.3", "human-to-milliseconds": "^1.0.0", "interface-datastore": "~0.4.2", - "ipfs-api": "^22.2.4", + "ipfs-api": "^23.0.0", "ipfs-bitswap": "~0.20.3", "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", From 072e5956d647bbc39a99151cc7f68e7234e2a174 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 16 Aug 2018 11:39:36 +0100 Subject: [PATCH 9/9] chore: update interface-ipfs-core dependency License: MIT Signed-off-by: Alan Shaw --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index febaa37653..3c9229f346 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "expose-loader": "~0.7.5", "form-data": "^2.3.2", "hat": "0.0.3", - "interface-ipfs-core": "~0.75.2", + "interface-ipfs-core": "~0.76.1", "ipfsd-ctl": "~0.39.1", "mocha": "^5.2.0", "ncp": "^2.0.0",