-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: ipns locally #1400
feat: ipns locally #1400
Changes from all commits
1723ffd
70e5b0a
ad27451
d819d10
8a06024
72dbb61
d9c8836
22e1bc8
7e19583
7bcc648
fab0b76
bc4a9ee
7f2aa3d
fdf3b90
8204f85
61f4d4e
08a952b
ba7585d
6e3c435
b2253e1
17bcdcb
1523579
04bf892
0515553
36e51fb
87e9d91
df4a276
49725ae
ca25516
23b5fbc
6bb6c97
7cd8fc5
84ee2c8
77caec1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use strict' | ||
|
||
/* | ||
IPNS is a PKI namespace, where names are the hashes of public keys, and | ||
the private key enables publishing new (signed) values. In both publish | ||
and resolve, the default name used is the node's own PeerID, | ||
which is the hash of its public key. | ||
*/ | ||
module.exports = { | ||
command: 'name <command>', | ||
|
||
description: 'Publish and resolve IPNS names.', | ||
|
||
builder (yargs) { | ||
return yargs.commandDir('name') | ||
}, | ||
|
||
handler (argv) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
'use strict' | ||
|
||
const print = require('../../utils').print | ||
|
||
module.exports = { | ||
command: 'publish <ipfsPath>', | ||
|
||
describe: 'Publish IPNS names.', | ||
|
||
builder: { | ||
resolve: { | ||
describe: 'Resolve given path before publishing. Default: true.', | ||
default: true | ||
}, | ||
lifetime: { | ||
alias: 't', | ||
describe: 'Time duration that the record will be valid for. Default: 24h.', | ||
default: '24h' | ||
}, | ||
key: { | ||
alias: 'k', | ||
describe: 'Name of the key to be used or a valid PeerID, as listed by "ipfs key list -l". Default: self.', | ||
default: 'self' | ||
}, | ||
ttl: { | ||
describe: 'Time duration this record should be cached for (caution: experimental).', | ||
default: '' | ||
} | ||
}, | ||
|
||
handler (argv) { | ||
const opts = { | ||
resolve: argv.resolve, | ||
lifetime: argv.lifetime, | ||
key: argv.key, | ||
ttl: argv.ttl | ||
} | ||
|
||
argv.ipfs.name.publish(argv.ipfsPath, opts, (err, result) => { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
print(`Published to ${result.name}: ${result.value}`) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
'use strict' | ||
|
||
const print = require('../../utils').print | ||
|
||
module.exports = { | ||
command: 'resolve [<name>]', | ||
|
||
describe: 'Resolve IPNS names.', | ||
|
||
builder: { | ||
nocache: { | ||
alias: 'n', | ||
describe: 'Do not use cached entries. Default: false.', | ||
default: false | ||
}, | ||
recursive: { | ||
alias: 'r', | ||
recursive: 'Resolve until the result is not an IPNS name. Default: false.', | ||
default: false | ||
} | ||
}, | ||
|
||
handler (argv) { | ||
const opts = { | ||
nocache: argv.nocache, | ||
recursive: argv.recursive | ||
} | ||
|
||
argv.ipfs.name.resolve(argv.name, opts, (err, result) => { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
if (result && result.path) { | ||
print(result.path) | ||
} else { | ||
print(result) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
'use strict' | ||
|
||
const debug = require('debug') | ||
const promisify = require('promisify-es6') | ||
const waterfall = require('async/waterfall') | ||
const parallel = require('async/parallel') | ||
const human = require('human-to-milliseconds') | ||
const crypto = require('libp2p-crypto') | ||
const errcode = require('err-code') | ||
|
||
const log = debug('jsipfs:name') | ||
log.error = debug('jsipfs:name:error') | ||
|
||
const utils = require('../utils') | ||
const path = require('../ipns/path') | ||
|
||
const keyLookup = (ipfsNode, kname, callback) => { | ||
if (kname === 'self') { | ||
return callback(null, ipfsNode._peerInfo.id.privKey) | ||
} | ||
|
||
const pass = ipfsNode._options.pass | ||
|
||
waterfall([ | ||
(cb) => ipfsNode._keychain.exportKey(kname, pass, cb), | ||
(pem, cb) => crypto.keys.import(pem, pass, cb) | ||
], (err, privateKey) => { | ||
if (err) { | ||
log.error(err) | ||
return callback(errcode(err, 'ERR_CANNOT_GET_KEY')) | ||
} | ||
|
||
return callback(null, privateKey) | ||
}) | ||
} | ||
|
||
module.exports = function name (self) { | ||
return { | ||
/** | ||
* IPNS is a PKI namespace, where names are the hashes of public keys, and | ||
* the private key enables publishing new (signed) values. In both publish | ||
* and resolve, the default name used is the node's own PeerID, | ||
* which is the hash of its public key. | ||
* | ||
* @param {String} value ipfs path of the object to be published. | ||
* @param {Object} options ipfs publish options. | ||
* @param {boolean} options.resolve resolve given path before publishing. | ||
* @param {String} options.lifetime time duration that the record will be valid for. | ||
This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are | ||
"ns", "ms", "s", "m", "h". Default is 24h. | ||
* @param {String} options.ttl time duration this record should be cached for (NOT IMPLEMENTED YET). | ||
* This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are | ||
"ns", "ms", "s", "m", "h" (caution: experimental). | ||
* @param {String} options.key name of the key to be used or a valid PeerID, as listed by 'ipfs key list -l'. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
*/ | ||
publish: promisify((value, options, callback) => { | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = {} | ||
} | ||
|
||
options = options || {} | ||
const resolve = !(options.resolve === false) | ||
const lifetime = options.lifetime || '24h' | ||
const key = options.key || 'self' | ||
|
||
if (!self.isOnline()) { | ||
const errMsg = utils.OFFLINE_ERROR | ||
|
||
log.error(errMsg) | ||
return callback(errcode(errMsg, 'OFFLINE_ERROR')) | ||
} | ||
|
||
// TODO: params related logic should be in the core implementation | ||
|
||
// Normalize path value | ||
try { | ||
value = utils.normalizePath(value) | ||
} catch (err) { | ||
log.error(err) | ||
return callback(err) | ||
} | ||
|
||
parallel([ | ||
(cb) => human(lifetime, cb), | ||
// (cb) => ttl ? human(ttl, cb) : cb(), | ||
(cb) => keyLookup(self, key, cb), | ||
// verify if the path exists, if not, an error will stop the execution | ||
(cb) => resolve.toString() === 'true' ? path.resolvePath(self, value, cb) : cb() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tasks can all be done in parallel |
||
], (err, results) => { | ||
if (err) { | ||
log.error(err) | ||
return callback(err) | ||
} | ||
|
||
// Calculate lifetime with nanoseconds precision | ||
const pubLifetime = results[0].toFixed(6) | ||
const privateKey = results[1] | ||
|
||
// TODO IMPROVEMENT - Handle ttl for cache | ||
// const ttl = results[1] | ||
// const privateKey = results[2] | ||
|
||
// Start publishing process | ||
self._ipns.publish(privateKey, value, pubLifetime, callback) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be using the resolved value here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t understand the resolved value. The result of: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. e.g.
Expectations:
Are my expectations incorrect? Also, there might be a bug in go-ipfs... ipfs/kubo#5368 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does the same thing as go-ipfs does: The docs could be better though 😕 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I understand now! |
||
}) | ||
}), | ||
|
||
/** | ||
* Given a key, query the DHT for its best value. | ||
* | ||
* @param {String} name ipns name to resolve. Defaults to your node's peerID. | ||
* @param {Object} options ipfs resolve options. | ||
* @param {boolean} options.nocache do not use cached entries. | ||
* @param {boolean} options.recursive resolve until the result is not an IPNS name. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
*/ | ||
resolve: promisify((name, options, callback) => { | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = {} | ||
} | ||
|
||
options = options || {} | ||
const nocache = options.nocache && options.nocache.toString() === 'true' | ||
const recursive = options.recursive && options.recursive.toString() === 'true' | ||
|
||
const local = true // TODO ROUTING - use self._options.local | ||
|
||
if (!self.isOnline() && !local) { | ||
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 (local && nocache) { | ||
const error = 'cannot specify both local and nocache' | ||
|
||
log.error(error) | ||
return callback(errcode(new Error(error), 'ERR_NOCACHE_AND_LOCAL')) | ||
} | ||
|
||
// Set node id as name for being resolved, if it is not received | ||
if (!name) { | ||
name = self._peerInfo.id.toB58String() | ||
} | ||
|
||
if (!name.startsWith('/ipns/')) { | ||
name = `/ipns/${name}` | ||
} | ||
|
||
const resolveOptions = { | ||
nocache, | ||
recursive, | ||
local | ||
} | ||
|
||
self._ipns.resolve(name, self._peerInfo.id, resolveOptions, callback) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless all of the above parameter validation is specific to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is also used here, for resolving |
||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to succeed and not have a
path
property?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that case happens when the daemon is offline