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

Commit 5044a30

Browse files
hugomrdiasAlan Shaw
authored and
Alan Shaw
committed
feat: add support for ipns name resolve /ipns/<fqdn> (#2002)
fixes: #1918 `ipns name resolve` dns tests moved to interface-core resolve call now returns a string as per documention.
1 parent 103e359 commit 5044a30

20 files changed

+311
-597
lines changed

.travis.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ os:
1818
- osx
1919
- windows
2020

21-
script: npx nyc -s npm run test:node --timeout=10000 -- --bail
21+
script: npx nyc -s npx aegir test -t node --timeout 10000 --bail
2222
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
2323

2424
jobs:
@@ -47,12 +47,12 @@ jobs:
4747
- stage: test
4848
name: electron-main
4949
script:
50-
- xvfb-run npx aegir test -t electron-main -- --bail
50+
- xvfb-run npx aegir test -t electron-main -- --bail --timeout 10000
5151

5252
- stage: test
5353
name: electron-renderer
5454
script:
55-
- xvfb-run npx aegir test -t electron-renderer -- --bail
55+
- xvfb-run npx aegir test -t electron-renderer -- --bail --timeout 10000
5656

5757
notifications:
5858
email: false

package.json

+6-3
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,13 @@
8787
"get-folder-size": "^2.0.0",
8888
"glob": "^7.1.3",
8989
"hapi-pino": "^6.0.0",
90+
"hashlru": "^2.3.0",
9091
"human-to-milliseconds": "^1.0.0",
9192
"interface-datastore": "~0.6.0",
9293
"ipfs-bitswap": "~0.24.1",
9394
"ipfs-block": "~0.8.1",
9495
"ipfs-block-service": "~0.15.1",
95-
"ipfs-http-client": "^32.0.0",
96+
"ipfs-http-client": "^32.0.1",
9697
"ipfs-http-response": "~0.3.0",
9798
"ipfs-mfs": "~0.11.4",
9899
"ipfs-multipart": "~0.1.0",
@@ -110,6 +111,7 @@
110111
"ipld-raw": "^4.0.0",
111112
"ipld-zcash": "~0.3.0",
112113
"ipns": "~0.5.2",
114+
"is-domain-name": "^1.0.1",
113115
"is-ipfs": "~0.6.1",
114116
"is-pull-stream": "~0.0.0",
115117
"is-stream": "^2.0.0",
@@ -176,15 +178,16 @@
176178
"aegir": "^19.0.3",
177179
"base64url": "^3.0.1",
178180
"chai": "^4.2.0",
181+
"clear-module": "^3.2.0",
179182
"delay": "^4.1.0",
180183
"detect-node": "^2.0.4",
181184
"dir-compare": "^1.4.0",
182185
"dirty-chai": "^2.0.1",
183186
"execa": "^1.0.0",
184187
"form-data": "^2.3.3",
185188
"hat": "0.0.3",
186-
"interface-ipfs-core": "~0.104.0",
187-
"ipfsd-ctl": "~0.42.0",
189+
"interface-ipfs-core": "~0.105.0",
190+
"ipfsd-ctl": "~0.43.0",
188191
"libp2p-websocket-star": "~0.10.2",
189192
"ncp": "^2.0.0",
190193
"qs": "^6.5.2",

src/cli/commands/name/publish.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const print = require('../../utils').print
3+
const { print } = require('../../utils')
44

55
module.exports = {
66
command: 'publish <ipfsPath>',
@@ -11,36 +11,34 @@ module.exports = {
1111
resolve: {
1212
alias: 'r',
1313
describe: 'Resolve given path before publishing. Default: true.',
14-
default: true
14+
default: true,
15+
type: 'boolean'
1516
},
1617
lifetime: {
1718
alias: 't',
1819
describe: 'Time duration that the record will be valid for. Default: 24h.',
19-
default: '24h'
20+
default: '24h',
21+
type: 'string'
2022
},
2123
key: {
2224
alias: 'k',
2325
describe: 'Name of the key to be used, as listed by "ipfs key list -l". Default: self.',
24-
default: 'self'
26+
default: 'self',
27+
type: 'string'
2528
},
2629
ttl: {
2730
describe: 'Time duration this record should be cached for (caution: experimental).',
28-
default: ''
31+
default: '',
32+
type: 'string'
2933
}
3034
},
3135

3236
handler (argv) {
3337
argv.resolve((async () => {
3438
// yargs-promise adds resolve/reject properties to argv
3539
// resolve should use the alias as resolve will always be overwritten to a function
36-
let resolve = true
37-
38-
if (argv.r === false || argv.r === 'false') {
39-
resolve = false
40-
}
41-
4240
const opts = {
43-
resolve,
41+
resolve: argv.r,
4442
lifetime: argv.lifetime,
4543
key: argv.key,
4644
ttl: argv.ttl

src/cli/commands/name/resolve.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module.exports = {
1818
type: 'boolean',
1919
alias: 'r',
2020
describe: 'Resolve until the result is not an IPNS name. Default: false.',
21-
default: false
21+
default: true
2222
}
2323
},
2424

@@ -32,11 +32,7 @@ module.exports = {
3232
const ipfs = await argv.getIpfs()
3333
const result = await ipfs.name.resolve(argv.name, opts)
3434

35-
if (result && result.path) {
36-
print(result.path)
37-
} else {
38-
print(result)
39-
}
35+
print(result)
4036
})())
4137
}
4238
}

src/core/components/name.js

+50-16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const parallel = require('async/parallel')
77
const human = require('human-to-milliseconds')
88
const crypto = require('libp2p-crypto')
99
const errcode = require('err-code')
10+
const mergeOptions = require('merge-options')
11+
const mh = require('multihashes')
12+
const isDomain = require('is-domain-name')
1013

1114
const log = debug('ipfs:name')
1215
log.error = debug('ipfs:name:error')
@@ -35,6 +38,28 @@ const keyLookup = (ipfsNode, kname, callback) => {
3538
})
3639
}
3740

41+
const appendRemainder = (cb, remainder) => {
42+
return (err, result) => {
43+
if (err) {
44+
return cb(err)
45+
}
46+
if (remainder.length) {
47+
return cb(null, result + '/' + remainder.join('/'))
48+
}
49+
return cb(null, result)
50+
}
51+
}
52+
53+
/**
54+
* @typedef { import("../index") } IPFS
55+
*/
56+
57+
/**
58+
* IPNS - Inter-Planetary Naming System
59+
*
60+
* @param {IPFS} self
61+
* @returns {Object}
62+
*/
3863
module.exports = function name (self) {
3964
return {
4065
/**
@@ -125,22 +150,15 @@ module.exports = function name (self) {
125150
options = {}
126151
}
127152

128-
options = options || {}
129-
const nocache = options.nocache && options.nocache.toString() === 'true'
130-
const recursive = options.recursive && options.recursive.toString() === 'true'
153+
options = mergeOptions({
154+
nocache: false,
155+
recursive: true
156+
}, options)
131157

132158
const offline = self._options.offline
133159

134-
if (!self.isOnline() && !offline) {
135-
const errMsg = utils.OFFLINE_ERROR
136-
137-
log.error(errMsg)
138-
return callback(errcode(errMsg, 'OFFLINE_ERROR'))
139-
}
140-
141160
// TODO: params related logic should be in the core implementation
142-
143-
if (offline && nocache) {
161+
if (offline && options.nocache) {
144162
const error = 'cannot specify both offline and nocache'
145163

146164
log.error(error)
@@ -156,12 +174,28 @@ module.exports = function name (self) {
156174
name = `/ipns/${name}`
157175
}
158176

159-
const resolveOptions = {
160-
nocache,
161-
recursive
177+
const [ namespace, hash, ...remainder ] = name.slice(1).split('/')
178+
try {
179+
mh.fromB58String(hash)
180+
} catch (err) {
181+
// lets check if we have a domain ex. /ipns/ipfs.io and resolve with dns
182+
if (isDomain(hash)) {
183+
return self.dns(hash, options, appendRemainder(callback, remainder))
184+
}
185+
186+
log.error(err)
187+
return callback(errcode(new Error('Invalid IPNS name.'), 'ERR_IPNS_INVALID_NAME'))
162188
}
163189

164-
self._ipns.resolve(name, resolveOptions, callback)
190+
// multihash is valid lets resolve with IPNS
191+
// IPNS resolve needs a online daemon
192+
if (!self.isOnline() && !offline) {
193+
const errMsg = utils.OFFLINE_ERROR
194+
195+
log.error(errMsg)
196+
return callback(errcode(errMsg, 'OFFLINE_ERROR'))
197+
}
198+
self._ipns.resolve(`/${namespace}/${hash}`, options, appendRemainder(callback, remainder))
165199
}),
166200
pubsub: namePubsub(self)
167201
}

src/core/index.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ const defaultRepo = require('./runtime/repo-nodejs')
2626
const preload = require('./preload')
2727
const mfsPreload = require('./mfs-preload')
2828
const ipldOptions = require('./runtime/ipld-nodejs')
29-
29+
/**
30+
* @typedef { import("./ipns/index") } IPNS
31+
*/
32+
33+
/**
34+
*
35+
*
36+
* @class IPFS
37+
* @extends {EventEmitter}
38+
*/
3039
class IPFS extends EventEmitter {
3140
constructor (options) {
3241
super()
@@ -76,6 +85,7 @@ class IPFS extends EventEmitter {
7685
this._ipld = new Ipld(ipldOptions(this._blockService, this._options.ipld, this.log))
7786
this._preload = preload(this)
7887
this._mfsPreload = mfsPreload(this)
88+
/** @type {IPNS} */
7989
this._ipns = undefined
8090
// eslint-disable-next-line no-console
8191
this._print = this._options.silent ? this.log : console.log

src/core/ipns/index.js

+19-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const { createFromPrivKey } = require('peer-id')
44
const series = require('async/series')
5-
const Receptacle = require('receptacle')
65

76
const errcode = require('err-code')
87
const debug = require('debug')
@@ -13,20 +12,28 @@ const IpnsPublisher = require('./publisher')
1312
const IpnsRepublisher = require('./republisher')
1413
const IpnsResolver = require('./resolver')
1514
const path = require('./path')
16-
15+
const { normalizePath } = require('../utils')
16+
const TLRU = require('../../utils/tlru')
1717
const defaultRecordTtl = 60 * 1000
1818

1919
class IPNS {
2020
constructor (routing, datastore, peerInfo, keychain, options) {
2121
this.publisher = new IpnsPublisher(routing, datastore)
2222
this.republisher = new IpnsRepublisher(this.publisher, datastore, peerInfo, keychain, options)
2323
this.resolver = new IpnsResolver(routing)
24-
this.cache = new Receptacle({ max: 1000 }) // Create an LRU cache with max 1000 items
24+
this.cache = new TLRU(1000)
2525
this.routing = routing
2626
}
2727

2828
// Publish
29-
publish (privKey, value, lifetime, callback) {
29+
publish (privKey, value, lifetime = IpnsPublisher.defaultRecordLifetime, callback) {
30+
try {
31+
value = normalizePath(value)
32+
} catch (err) {
33+
log.error(err)
34+
return callback(err)
35+
}
36+
3037
series([
3138
(cb) => createFromPrivKey(privKey.bytes, cb),
3239
(cb) => this.publisher.publishWithEOL(privKey, value, lifetime, cb)
@@ -38,12 +45,12 @@ class IPNS {
3845

3946
log(`IPNS value ${value} was published correctly`)
4047

41-
// Add to cache
48+
// // Add to cache
4249
const id = results[0].toB58String()
4350
const ttEol = parseFloat(lifetime)
4451
const ttl = (ttEol < defaultRecordTtl) ? ttEol : defaultRecordTtl
4552

46-
this.cache.set(id, value, { ttl: ttl })
53+
this.cache.set(id, value, ttl)
4754

4855
log(`IPNS value ${value} was cached correctly`)
4956

@@ -77,9 +84,7 @@ class IPNS {
7784
const result = this.cache.get(id)
7885

7986
if (result) {
80-
return callback(null, {
81-
path: result
82-
})
87+
return callback(null, result)
8388
}
8489
}
8590

@@ -91,18 +96,17 @@ class IPNS {
9196

9297
log(`IPNS record from ${name} was resolved correctly`)
9398

94-
callback(null, {
95-
path: result
96-
})
99+
callback(null, result)
97100
})
98101
}
99102

100103
// Initialize keyspace
101104
// sets the ipns record for the given key to point to an empty directory
102105
initializeKeyspace (privKey, value, callback) {
103-
this.publisher.publish(privKey, value, callback)
106+
this.publish(privKey, value, IpnsPublisher.defaultRecordLifetime, callback)
104107
}
105108
}
106109

107-
exports = module.exports = IPNS
108-
exports.path = path
110+
IPNS.path = path
111+
112+
module.exports = IPNS

src/core/ipns/publisher.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ log.error = debug('ipfs:ipns:publisher:error')
1111

1212
const ipns = require('ipns')
1313

14-
const defaultRecordTtl = 60 * 60 * 1000
14+
const defaultRecordLifetime = 60 * 60 * 1000
1515

1616
// IpnsPublisher is capable of publishing and resolving names to the IPFS routing system.
1717
class IpnsPublisher {
@@ -46,7 +46,7 @@ class IpnsPublisher {
4646

4747
// Accepts a keypair, as well as a value (ipfsPath), and publishes it out to the routing system
4848
publish (privKey, value, callback) {
49-
this.publishWithEOL(privKey, value, defaultRecordTtl, callback)
49+
this.publishWithEOL(privKey, value, defaultRecordLifetime, callback)
5050
}
5151

5252
_putRecordToRouting (record, peerId, callback) {
@@ -269,4 +269,5 @@ class IpnsPublisher {
269269
}
270270
}
271271

272+
IpnsPublisher.defaultRecordLifetime = defaultRecordLifetime
272273
exports = module.exports = IpnsPublisher

src/http/api/resources/name.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ exports.resolve = {
77
query: Joi.object().keys({
88
arg: Joi.string(),
99
nocache: Joi.boolean().default(false),
10-
recursive: Joi.boolean().default(false)
10+
recursive: Joi.boolean().default(true)
1111
}).unknown()
1212
},
1313
async handler (request, h) {
@@ -17,7 +17,7 @@ exports.resolve = {
1717
const res = await ipfs.name.resolve(arg, request.query)
1818

1919
return h.response({
20-
Path: res.path
20+
Path: res
2121
})
2222
}
2323
}

0 commit comments

Comments
 (0)