Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 4df45d1

Browse files
alanshawdaviddias
authored andcommitted
feat: add partial implementation for ipfs.resolve (#1455)
* feat: add partial implementation for ipfs.resolve License: MIT Signed-off-by: Alan Shaw <[email protected]> * feat: WIP add ipfs.resolve CLI and HTTP command License: MIT Signed-off-by: Alan Shaw <[email protected]> * test: add resolve IPFS path test License: MIT Signed-off-by: Alan Shaw <[email protected]> * fix: command count for tests License: MIT Signed-off-by: Alan Shaw <[email protected]> * fix: skip tests that do not pass on js-ipfs yet License: MIT Signed-off-by: Alan Shaw <[email protected]> * fix: increase timeout for resolve tests License: MIT Signed-off-by: Alan Shaw <[email protected]> * fix: remove .only License: MIT Signed-off-by: Alan Shaw <[email protected]> * chore: update ipfs-api dependency License: MIT Signed-off-by: Alan Shaw <[email protected]> * chore: update interface-ipfs-core dependency License: MIT Signed-off-by: Alan Shaw <[email protected]>
1 parent 3d2a874 commit 4df45d1

File tree

12 files changed

+236
-5
lines changed

12 files changed

+236
-5
lines changed

src/cli/commands/resolve.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict'
2+
3+
const print = require('../utils').print
4+
5+
module.exports = {
6+
command: 'resolve <name>',
7+
8+
description: 'Resolve the value of names to IPFS',
9+
10+
builder: {
11+
recursive: {
12+
alias: 'r',
13+
type: 'boolean',
14+
default: false
15+
}
16+
},
17+
18+
handler (argv) {
19+
argv.ipfs.resolve(argv.name, { recursive: argv.recursive }, (err, res) => {
20+
if (err) throw err
21+
print(res)
22+
})
23+
}
24+
}

src/core/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ exports.dns = require('./dns')
2727
exports.key = require('./key')
2828
exports.stats = require('./stats')
2929
exports.mfs = require('./mfs')
30+
exports.resolve = require('./resolve')

src/core/components/resolve.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use strict'
2+
3+
const promisify = require('promisify-es6')
4+
const isIpfs = require('is-ipfs')
5+
const setImmediate = require('async/setImmediate')
6+
const doUntil = require('async/doUntil')
7+
const CID = require('cids')
8+
9+
module.exports = (self) => {
10+
return promisify((name, opts, cb) => {
11+
if (typeof opts === 'function') {
12+
cb = opts
13+
opts = {}
14+
}
15+
16+
opts = opts || {}
17+
18+
if (!isIpfs.path(name)) {
19+
return setImmediate(() => cb(new Error('invalid argument')))
20+
}
21+
22+
// TODO remove this and update subsequent code when IPNS is implemented
23+
if (!isIpfs.ipfsPath(name)) {
24+
return setImmediate(() => cb(new Error('resolve non-IPFS names is not implemented')))
25+
}
26+
27+
const split = name.split('/') // ['', 'ipfs', 'hash', ...path]
28+
const cid = new CID(split[2])
29+
30+
if (split.length === 3) {
31+
return setImmediate(() => cb(null, name))
32+
}
33+
34+
const path = split.slice(3).join('/')
35+
36+
resolve(cid, path, (err, cid) => {
37+
if (err) return cb(err)
38+
if (!cid) return cb(new Error('found non-link at given path'))
39+
cb(null, `/ipfs/${cid.toBaseEncodedString(opts.cidBase)}`)
40+
})
41+
})
42+
43+
// Resolve the given CID + path to a CID.
44+
function resolve (cid, path, callback) {
45+
let value
46+
47+
doUntil(
48+
(cb) => {
49+
self.block.get(cid, (err, block) => {
50+
if (err) return cb(err)
51+
52+
const r = self._ipld.resolvers[cid.codec]
53+
54+
if (!r) {
55+
return cb(new Error(`No resolver found for codec "${cid.codec}"`))
56+
}
57+
58+
r.resolver.resolve(block.data, path, (err, result) => {
59+
if (err) return cb(err)
60+
value = result.value
61+
path = result.remainderPath
62+
cb()
63+
})
64+
})
65+
},
66+
() => {
67+
const endReached = !path || path === '/'
68+
69+
if (endReached) {
70+
return true
71+
}
72+
73+
if (value) {
74+
cid = new CID(value['/'])
75+
}
76+
77+
return false
78+
},
79+
(err) => {
80+
if (err) return callback(err)
81+
if (value && value['/']) return callback(null, new CID(value['/']))
82+
callback()
83+
}
84+
)
85+
}
86+
}

src/core/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ class IPFS extends EventEmitter {
120120
this.dns = components.dns(this)
121121
this.key = components.key(this)
122122
this.stats = components.stats(this)
123+
this.resolve = components.resolve(this)
123124

124125
if (this._options.EXPERIMENTAL.pubsub) {
125126
this.log('EXPERIMENTAL pubsub is enabled')

src/http/api/resources/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ exports.pubsub = require('./pubsub')
1818
exports.dns = require('./dns')
1919
exports.key = require('./key')
2020
exports.stats = require('./stats')
21+
exports.resolve = require('./resolve')

src/http/api/resources/resolve.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
const Joi = require('joi')
4+
const debug = require('debug')
5+
6+
const log = debug('jsipfs:http-api:resolve')
7+
log.error = debug('jsipfs:http-api:resolve:error')
8+
9+
module.exports = {
10+
validate: {
11+
query: Joi.object().keys({
12+
r: Joi.alternatives()
13+
.when('recursive', {
14+
is: Joi.any().exist(),
15+
then: Joi.any().forbidden(),
16+
otherwise: Joi.boolean()
17+
}),
18+
recursive: Joi.boolean(),
19+
arg: Joi.string().required()
20+
}).unknown()
21+
},
22+
handler (request, reply) {
23+
const ipfs = request.server.app.ipfs
24+
const name = request.query.arg
25+
const recursive = request.query.r || request.query.recursive || false
26+
27+
log(name, { recursive })
28+
29+
ipfs.resolve(name, { recursive }, (err, res) => {
30+
if (err) {
31+
log.error(err)
32+
return reply({ Message: err.message, Code: 0 }).code(500)
33+
}
34+
reply({ Path: res })
35+
})
36+
}
37+
}

src/http/api/routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ module.exports = (server) => {
2121
require('./dns')(server)
2222
require('./key')(server)
2323
require('./stats')(server)
24+
require('./resolve')(server)
2425
}

src/http/api/routes/resolve.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict'
2+
3+
const resources = require('./../resources')
4+
5+
module.exports = (server) => {
6+
const api = server.select('API')
7+
8+
api.route({
9+
method: '*',
10+
path: '/api/v0/resolve',
11+
config: {
12+
handler: resources.resolve.handler,
13+
validate: resources.resolve.validate
14+
}
15+
})
16+
}

test/cli/commands.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
const expect = require('chai').expect
55
const runOnAndOff = require('../utils/on-and-off')
66

7-
const commandCount = 77
7+
const commandCount = 78
88

99
describe('commands', () => runOnAndOff((thing) => {
1010
let ipfs

test/cli/resolve.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const path = require('path')
5+
const expect = require('chai').expect
6+
const isIpfs = require('is-ipfs')
7+
8+
const runOnAndOff = require('../utils/on-and-off')
9+
10+
describe('resolve', () => runOnAndOff((thing) => {
11+
let ipfs
12+
13+
before(() => {
14+
ipfs = thing.ipfs
15+
})
16+
17+
it('should resolve an IPFS hash', function () {
18+
this.timeout(10 * 1000)
19+
20+
const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme')
21+
let hash
22+
23+
return ipfs(`add ${filePath}`)
24+
.then((out) => {
25+
hash = out.split(' ')[1]
26+
expect(isIpfs.cid(hash)).to.be.true()
27+
return ipfs(`resolve /ipfs/${hash}`)
28+
})
29+
.then((out) => {
30+
expect(out).to.contain(`/ipfs/${hash}`)
31+
})
32+
})
33+
34+
it('should resolve an IPFS path link', function () {
35+
this.timeout(10 * 1000)
36+
37+
const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme')
38+
let fileHash, rootHash
39+
40+
return ipfs(`add ${filePath} --wrap-with-directory`)
41+
.then((out) => {
42+
const lines = out.split('\n')
43+
44+
fileHash = lines[0].split(' ')[1]
45+
rootHash = lines[1].split(' ')[1]
46+
47+
expect(isIpfs.cid(fileHash)).to.be.true()
48+
expect(isIpfs.cid(rootHash)).to.be.true()
49+
50+
return ipfs(`resolve /ipfs/${rootHash}/readme`)
51+
})
52+
.then((out) => {
53+
expect(out).to.contain(`/ipfs/${fileHash}`)
54+
})
55+
})
56+
}))

test/core/interface.spec.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ describe('interface-ipfs-core tests', () => {
5555
}), {
5656
skip: [
5757
{
58-
name: 'resolve',
59-
reason: 'TODO: not implemented'
58+
name: 'should resolve an IPNS DNS link',
59+
reason: 'TODO IPNS not implemented yet'
60+
},
61+
{
62+
name: 'should resolve IPNS link recursively',
63+
reason: 'TODO IPNS not implemented yet'
6064
}
6165
]
6266
})

test/http-api/interface.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ describe('interface-ipfs-core over ipfs-api tests', () => {
4040
}), {
4141
skip: [
4242
{
43-
name: 'resolve',
44-
reason: 'TODO: not implemented'
43+
name: 'should resolve an IPNS DNS link',
44+
reason: 'TODO IPNS not implemented yet'
45+
},
46+
{
47+
name: 'should resolve IPNS link recursively',
48+
reason: 'TODO IPNS not implemented yet'
4549
}
4650
]
4751
})

0 commit comments

Comments
 (0)