diff --git a/.gitignore b/.gitignore index 18ddbab6a9..7455bdffdb 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ test/test-data/go-ipfs-repo/LOG.old # while testing npm5 package-lock.json +yarn.lock diff --git a/package.json b/package.json index 52fa0192ff..7ec29b7b17 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "./src/core/runtime/config-nodejs.json": "./src/core/runtime/config-browser.json", "./src/core/runtime/libp2p-nodejs.js": "./src/core/runtime/libp2p-browser.js", "./src/core/runtime/repo-nodejs.js": "./src/core/runtime/repo-browser.js", + "./src/core/runtime/dns-nodejs.js": "./src/core/runtime/dns-browser.js", "./test/utils/create-repo-nodejs.js": "./test/utils/create-repo-browser.js", "stream": "readable-stream" }, @@ -66,14 +67,14 @@ "dir-compare": "^1.4.0", "dirty-chai": "^2.0.1", "eslint-plugin-react": "^7.5.1", - "execa": "^0.8.0", + "execa": "^0.9.0", "expose-loader": "^0.7.4", "form-data": "^2.3.1", "hat": "0.0.3", - "interface-ipfs-core": "~0.36.15", + "interface-ipfs-core": "~0.40.0", "left-pad": "^1.2.0", "lodash": "^4.17.4", - "mocha": "^4.0.1", + "mocha": "^4.1.0", "ncp": "^2.0.0", "nexpect": "^0.5.0", "pre-commit": "^1.2.2", @@ -91,28 +92,28 @@ "boom": "^7.1.1", "bs58": "^4.0.1", "byteman": "^1.3.5", - "cids": "^0.5.2", + "cids": "~0.5.2", "debug": "^3.1.0", "file-type": "^7.4.0", "filesize": "^3.5.11", "fsm-event": "^2.1.0", - "get-folder-size": "^1.0.0", + "get-folder-size": "^1.0.1", "glob": "^7.1.2", - "hapi": "^16.2.2", + "hapi": "^16.6.2", "hapi-set-header": "^1.0.2", "hoek": "^5.0.2", - "ipfs-api": "^17.2.4", + "ipfs-api": "^17.3.0", "ipfs-bitswap": "~0.18.0", "ipfs-block": "~0.6.1", "ipfs-block-service": "~0.13.0", "ipfs-multipart": "~0.1.0", - "ipfs-repo": "~0.18.4", + "ipfs-repo": "~0.18.5", "ipfs-unixfs": "~0.1.14", - "ipfs-unixfs-engine": "~0.24.1", + "ipfs-unixfs-engine": "~0.24.2", "ipld-resolver": "~0.14.1", "is-ipfs": "^0.3.2", "is-stream": "^1.1.0", - "joi": "^13.0.2", + "joi": "^13.1.0", "libp2p": "~0.15.0", "libp2p-circuit": "~0.1.4", "libp2p-floodsub": "~0.13.1", @@ -121,7 +122,7 @@ "libp2p-multiplex": "~0.5.1", "libp2p-railing": "~0.7.1", "libp2p-secio": "~0.9.0", - "libp2p-tcp": "~0.11.1", + "libp2p-tcp": "~0.11.2", "libp2p-webrtc-star": "~0.13.3", "libp2p-websocket-star": "~0.7.2", "libp2p-websockets": "~0.10.4", @@ -132,13 +133,13 @@ "mafmt": "^3.0.2", "mime-types": "^2.1.17", "mkdirp": "~0.5.1", - "multiaddr": "^3.0.1", - "multihashes": "~0.4.12", + "multiaddr": "^3.0.2", + "multihashes": "~0.4.13", "once": "^1.4.0", "path-exists": "^3.0.0", "peer-book": "~0.5.2", - "peer-id": "~0.10.3", - "peer-info": "~0.11.3", + "peer-id": "~0.10.4", + "peer-info": "~0.11.4", "progress": "^2.0.0", "promisify-es6": "^1.0.3", "pull-abortable": "^4.1.1", @@ -146,7 +147,7 @@ "pull-file": "^1.1.0", "pull-ndjson": "^0.1.1", "pull-paramap": "^1.2.2", - "pull-pushable": "^2.1.1", + "pull-pushable": "^2.1.2", "pull-sort": "^1.0.1", "pull-stream": "^3.6.1", "pull-stream-to-stream": "^1.3.4", @@ -159,8 +160,8 @@ "temp": "~0.8.3", "through2": "^2.0.3", "update-notifier": "^2.3.0", - "yargs": "^10.0.3", - "yargs-parser": "^8.0.0" + "yargs": "^10.1.1", + "yargs-parser": "^8.1.0" }, "optionalDependencies": { "prom-client": "^10.2.2", diff --git a/src/cli/commands/dns.js b/src/cli/commands/dns.js new file mode 100644 index 0000000000..5be09d12e1 --- /dev/null +++ b/src/cli/commands/dns.js @@ -0,0 +1,24 @@ +'use strict' +const print = require('../utils').print + +module.exports = { + command: 'dns ', + + describe: 'Resolve DNS links', + + builder: { + format: { + type: 'string' + } + }, + + handler (argv) { + argv.ipfs.dns(argv['domain'], (err, path) => { + if (err) { + throw err + } + + print(path) + }) + } +} diff --git a/src/core/components/dns.js b/src/core/components/dns.js new file mode 100644 index 0000000000..4895f01667 --- /dev/null +++ b/src/core/components/dns.js @@ -0,0 +1,20 @@ +'use strict' + +// dns-nodejs gets replaced by dns-browser when webpacked/browserified +const dns = require('../runtime/dns-nodejs') +const promisify = require('promisify-es6') + +module.exports = () => { + return promisify((domain, opts, callback) => { + if (typeof domain !== 'string') { + return callback(new Error('Invalid arguments, domain must be a string')) + } + + if (typeof opts === 'function') { + callback = opts + opts = {} + } + + dns(domain, opts, callback) + }) +} diff --git a/src/core/components/index.js b/src/core/components/index.js index e4fb45464f..248243a88e 100644 --- a/src/core/components/index.js +++ b/src/core/components/index.js @@ -20,3 +20,4 @@ exports.files = require('./files') exports.bitswap = require('./bitswap') exports.pubsub = require('./pubsub') exports.dht = require('./dht') +exports.dns = require('./dns') diff --git a/src/core/index.js b/src/core/index.js index 2fb4825e54..fa60876061 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -94,6 +94,7 @@ class IPFS extends EventEmitter { this.ping = components.ping(this) this.pubsub = components.pubsub(this) this.dht = components.dht(this) + this.dns = components.dns(this) if (this._options.EXPERIMENTAL.pubsub) { this.log('EXPERIMENTAL pubsub is enabled') diff --git a/src/core/runtime/dns-browser.js b/src/core/runtime/dns-browser.js new file mode 100644 index 0000000000..c68de233fe --- /dev/null +++ b/src/core/runtime/dns-browser.js @@ -0,0 +1,25 @@ +'use strict' + +module.exports = (domain, opts, callback) => { + domain = encodeURIComponent(domain) + let url = `https://ipfs.io/api/v0/dns?arg=${domain}` + + for (const prop in opts) { + url += `&${prop}=${opts[prop]}` + } + + self.fetch(url, {mode: 'cors'}) + .then((response) => { + return response.json() + }) + .then((response) => { + if (response.Path) { + return callback(null, response.Path) + } else { + return callback(new Error(response.Message)) + } + }) + .catch((error) => { + callback(error) + }) +} diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js new file mode 100644 index 0000000000..8f6bb132bd --- /dev/null +++ b/src/core/runtime/dns-nodejs.js @@ -0,0 +1,21 @@ +'use strict' + +const dns = require('dns') + +module.exports = (domain, opts, callback) => { + dns.resolveTxt(domain, (err, records) => { + if (err) { + return callback(err, null) + } + + // TODO: implement recursive option + + for (const record of records) { + if (record[0].startsWith('dnslink=')) { + return callback(null, record[0].substr(8, record[0].length - 1)) + } + } + + callback(new Error('domain does not have a txt dnslink entry')) + }) +} diff --git a/src/http/api/resources/dns.js b/src/http/api/resources/dns.js new file mode 100644 index 0000000000..92549e68b7 --- /dev/null +++ b/src/http/api/resources/dns.js @@ -0,0 +1,24 @@ +'use strict' + +const boom = require('boom') + +exports = module.exports + +exports.get = (request, reply) => { + if (!request.query.arg) { + return reply({ + Message: "Argument 'domain' is required", + Code: 0 + }).code(400).takeover() + } + + request.server.app.ipfs.dns(request.query.arg, (err, path) => { + if (err) { + return reply(boom.badRequest(err)) + } + + return reply({ + Path: path + }) + }) +} diff --git a/src/http/api/resources/index.js b/src/http/api/resources/index.js index 37bb316cab..16b0f17128 100644 --- a/src/http/api/resources/index.js +++ b/src/http/api/resources/index.js @@ -12,3 +12,4 @@ exports.bitswap = require('./bitswap') exports.file = require('./file') exports.files = require('./files') exports.pubsub = require('./pubsub') +exports.dns = require('./dns') diff --git a/src/http/api/routes/dns.js b/src/http/api/routes/dns.js new file mode 100644 index 0000000000..dd3f074025 --- /dev/null +++ b/src/http/api/routes/dns.js @@ -0,0 +1,13 @@ +'use strict' + +const resources = require('./../resources') + +module.exports = (server) => { + const api = server.select('API') + + api.route({ + method: '*', + path: '/api/v0/dns', + handler: resources.dns.get + }) +} diff --git a/src/http/api/routes/index.js b/src/http/api/routes/index.js index 08756fc91c..908c0c0878 100644 --- a/src/http/api/routes/index.js +++ b/src/http/api/routes/index.js @@ -15,4 +15,5 @@ module.exports = (server) => { require('./pubsub')(server) require('./debug')(server) require('./webui')(server) + require('./dns')(server) } diff --git a/test/cli/commands.js b/test/cli/commands.js index 558cbcc8b4..3a2d85a418 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 = 59 +const commandCount = 60 describe('commands', () => runOnAndOff((thing) => { let ipfs diff --git a/test/cli/dns.js b/test/cli/dns.js new file mode 100644 index 0000000000..ae66c6c8c3 --- /dev/null +++ b/test/cli/dns.js @@ -0,0 +1,22 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const runOnAndOff = require('../utils/on-and-off') + +describe('dns', () => runOnAndOff((thing) => { + let ipfs + + before(function () { + this.timeout(60 * 1000) + ipfs = thing.ipfs + }) + + it('resolve ipfs.io dns', function () { + this.timeout(60 * 1000) + + return ipfs('dns ipfs.io').then((res) => { + expect(res.substr(0, 6)).to.eql('/ipfs/') + }) + }) +})) diff --git a/test/http-api/extra/dns.js b/test/http-api/extra/dns.js new file mode 100644 index 0000000000..bb6ab6046f --- /dev/null +++ b/test/http-api/extra/dns.js @@ -0,0 +1,19 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +module.exports = (ctl) => { + describe('.dns', () => { + it('resolve ipfs.io dns', (done) => { + ctl.dns('ipfs.io', (err, result) => { + expect(err).to.not.exist() + expect(result).to.exist() + done() + }) + }) + }) +} diff --git a/test/http-api/spec/dns.js b/test/http-api/spec/dns.js new file mode 100644 index 0000000000..bf84eb6b07 --- /dev/null +++ b/test/http-api/spec/dns.js @@ -0,0 +1,24 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect + +module.exports = (http) => { + describe('/dns', () => { + let api + + before(() => { + api = http.api.server.select('API') + }) + + it('resolve ipfs.io dns', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/dns?arg=ipfs.io' + }, (res) => { + expect(res.result).to.have.property('Path') + done() + }) + }) + }) +}