diff --git a/package.json b/package.json index 3436d3359e..f4c911a8b9 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "ipld-raw": "^2.0.1", "ipld-zcash": "~0.1.6", "ipns": "~0.5.0", + "is-domain-name": "^1.0.1", "is-ipfs": "~0.6.0", "is-pull-stream": "~0.0.0", "is-stream": "^1.1.0", diff --git a/src/core/components/name.js b/src/core/components/name.js index aa43bd2a77..d16810ddd2 100644 --- a/src/core/components/name.js +++ b/src/core/components/name.js @@ -7,6 +7,9 @@ const parallel = require('async/parallel') const human = require('human-to-milliseconds') const crypto = require('libp2p-crypto') const errcode = require('err-code') +const mergeOptions = require('merge-options') +const mh = require('multihashes') +const isDomain = require('is-domain-name') const log = debug('ipfs:name') log.error = debug('ipfs:name:error') @@ -35,6 +38,16 @@ const keyLookup = (ipfsNode, kname, callback) => { }) } +/** + * @typedef { import("../index") } IPFS + */ + +/** + * IPNS - Inter-Planetary Naming System + * + * @param {IPFS} self + * @returns {Function} + */ module.exports = function name (self) { return { /** @@ -125,22 +138,15 @@ module.exports = function name (self) { options = {} } - options = options || {} - const nocache = options.nocache && options.nocache.toString() === 'true' - const recursive = options.recursive && options.recursive.toString() === 'true' + options = mergeOptions({ + nocache: false, + recursive: false + }, options) const offline = self._options.offline - if (!self.isOnline() && !offline) { - const errMsg = utils.OFFLINE_ERROR - - log.error(errMsg) - return callback(errcode(errMsg, 'OFFLINE_ERROR')) - } - // TODO: params related logic should be in the core implementation - - if (offline && nocache) { + if (offline && options.nocache) { const error = 'cannot specify both offline and nocache' log.error(error) @@ -156,12 +162,27 @@ module.exports = function name (self) { name = `/ipns/${name}` } - const resolveOptions = { - nocache, - recursive - } + const [ , hash ] = name.slice(1).split('/') + try { + mh.fromB58String(hash) + + // ipns resolve needs a online daemon + if (!self.isOnline() && !offline) { + const errMsg = utils.OFFLINE_ERROR - self._ipns.resolve(name, resolveOptions, callback) + log.error(errMsg) + return callback(errcode(errMsg, 'OFFLINE_ERROR')) + } + self._ipns.resolve(name, options, callback) + } catch (err) { + // lets check if we have a domain ex. /ipns/ipfs.io and resolve with dns + if (isDomain(hash)) { + return self.dns(hash, options, callback) + } + + log.error(err) + callback(errcode(new Error('Invalid IPNS name.'), 'ERR_IPNS_INVALID_NAME')) + } }), pubsub: namePubsub(self) } diff --git a/src/core/index.js b/src/core/index.js index d457cb6149..3245a6cb9a 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,7 +26,16 @@ const defaultRepo = require('./runtime/repo-nodejs') const preload = require('./preload') const mfsPreload = require('./mfs-preload') const ipldOptions = require('./runtime/ipld-nodejs') - +/** + * @typedef { import("./ipns/index") } IPNS + */ + +/** + * + * + * @class IPFS + * @extends {EventEmitter} + */ class IPFS extends EventEmitter { constructor (options) { super() @@ -76,6 +85,7 @@ class IPFS extends EventEmitter { this._ipld = new Ipld(ipldOptions(this._blockService, this._options.ipld, this.log)) this._preload = preload(this) this._mfsPreload = mfsPreload(this) + /** @type {IPNS} */ this._ipns = undefined // eslint-disable-next-line no-console this._print = this._options.silent ? this.log : console.log diff --git a/src/core/ipns/index.js b/src/core/ipns/index.js index a064ece4d5..6a9d710fd7 100644 --- a/src/core/ipns/index.js +++ b/src/core/ipns/index.js @@ -104,5 +104,6 @@ class IPNS { } } -exports = module.exports = IPNS -exports.path = path +IPNS.path = path + +module.exports = IPNS diff --git a/test/core/name.js b/test/core/name.js index 99b8257251..d9c1084ccf 100644 --- a/test/core/name.js +++ b/test/core/name.js @@ -123,7 +123,7 @@ describe('name', function () { }) }) - it('should not recursively resolve to an IPFS hash if the option recursive is not provided', function (done) { + it('should not recursively resolve to an IPFS hash', function (done) { this.timeout(90 * 1000) const keyName = hat() @@ -132,7 +132,7 @@ describe('name', function () { series([ (cb) => node.name.publish(ipfsRef, { resolve: false }, cb), (cb) => node.name.publish(`/ipns/${nodeId}`, { resolve: false, key: keyName }, cb), - (cb) => node.name.resolve(key.id, cb) + (cb) => node.name.resolve(key.id, { recursive: false }, cb) ], (err, res) => { expect(err).to.not.exist() expect(res[2]).to.exist() @@ -618,4 +618,43 @@ describe('name', function () { done() }) }) + + describe('working with dns', function () { + let node + let ipfsd + + before(function (done) { + df.spawn({ + exec: IPFS, + args: [`--pass ${hat()}`, '--offline'], + config: { Bootstrap: [] } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + node = _ipfsd.api + done() + }) + }) + + after((done) => ipfsd.stop(done)) + + it('should resolve ipfs.io', async () => { + const r = await node.name.resolve('ipfs.io', { recursive: false }) + return expect(r).to.eq('/ipns/website.ipfs.io') + }) + + it('should resolve /ipns/ipfs.io recursive', async () => { + const r = await node.name.resolve('ipfs.io', { recursive: true }) + + return expect(r.substr(0, 6)).to.eql('/ipfs/') + }) + + it('should fail to resolve /ipns/ipfs.a', async () => { + try { + await node.name.resolve('ipfs.a') + } catch (err) { + expect(err).to.exist() + } + }) + }) })