From 1f63e8c6c0659b7465d24aadc17c6d3f31a422c7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 25 Jul 2018 16:50:34 +0100 Subject: [PATCH 01/51] feat: support --raw-leaves (#1454) Also updates pinning to support CIDv1 --- package.json | 4 +-- src/cli/commands/files/add.js | 33 ++----------------- src/core/components/pin.js | 5 ++- src/core/utils.js | 13 +++++--- src/http/api/resources/files.js | 16 ++------- test/cli/files.js | 58 ++++++++++----------------------- 6 files changed, 35 insertions(+), 94 deletions(-) diff --git a/package.json b/package.json index 67d1819938..2cd7bcb477 100644 --- a/package.json +++ b/package.json @@ -108,11 +108,11 @@ "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", "ipfs-http-response": "~0.1.2", - "ipfs-mfs": "~0.1.0", + "ipfs-mfs": "~0.2.2", "ipfs-multipart": "~0.1.0", "ipfs-repo": "~0.22.1", "ipfs-unixfs": "~0.1.15", - "ipfs-unixfs-engine": "~0.30.0", + "ipfs-unixfs-engine": "~0.31.3", "ipld": "~0.17.3", "ipld-dag-cbor": "~0.12.1", "ipld-dag-pb": "~0.14.5", diff --git a/src/cli/commands/files/add.js b/src/cli/commands/files/add.js index fe2a602a41..2547150e91 100644 --- a/src/cli/commands/files/add.js +++ b/src/cli/commands/files/add.js @@ -145,12 +145,13 @@ module.exports = { }, 'raw-leaves': { type: 'boolean', - default: undefined, + default: false, describe: 'Use raw blocks for leaf nodes. (experimental)' }, 'cid-version': { type: 'integer', - describe: 'Cid version. Non-zero value will change default of \'raw-leaves\' to true. (experimental)' + describe: 'CID version. Defaults to 0 unless an option that depends on CIDv1 is passed. (experimental)', + default: 0 }, hash: { type: 'string', @@ -197,34 +198,6 @@ module.exports = { pin: argv.pin } - // Temporary restriction on raw-leaves: - // When cid-version=1 then raw-leaves MUST be present and false. - // - // This is because raw-leaves is not yet implemented in js-ipfs, - // and go-ipfs changes the value of raw-leaves to true when - // cid-version > 0 unless explicitly set to false. - // - // This retains feature parity without having to implement raw-leaves. - if (options.cidVersion > 0 && options.rawLeaves !== false) { - throw new Error('Implied argument raw-leaves must be passed and set to false when cid-version is > 0') - } - - // Temporary restriction on raw-leaves: - // When hash != undefined then raw-leaves MUST be present and false. - // - // This is because raw-leaves is not yet implemented in js-ipfs, - // and go-ipfs changes the value of raw-leaves to true when - // hash != undefined unless explicitly set to false. - // - // This retains feature parity without having to implement raw-leaves. - if (options.hash && options.rawLeaves !== false) { - throw new Error('Implied argument raw-leaves must be passed and set to false when hash argument is specified') - } - - if (options.rawLeaves) { - throw new Error('Not implemented: raw-leaves') - } - if (options.enableShardingExperiment && utils.isDaemonOn()) { throw new Error('Error: Enabling the sharding experiment should be done on the daemon') } diff --git a/src/core/components/pin.js b/src/core/components/pin.js index c6f6cc9696..d2bd670e14 100644 --- a/src/core/components/pin.js +++ b/src/core/components/pin.js @@ -4,7 +4,6 @@ const promisify = require('promisify-es6') const { DAGNode, DAGLink } = require('ipld-dag-pb') const CID = require('cids') -const multihashes = require('multihashes') const async = require('async') const { Key } = require('interface-datastore') @@ -34,9 +33,9 @@ module.exports = (self) => { let recursivePins = new Set() const directKeys = () => - Array.from(directPins).map(key => multihashes.fromB58String(key)) + Array.from(directPins).map(key => new CID(key).buffer) const recursiveKeys = () => - Array.from(recursivePins).map(key => multihashes.fromB58String(key)) + Array.from(recursivePins).map(key => new CID(key).buffer) function getIndirectKeys (callback) { const indirectKeys = new Set() diff --git a/src/core/utils.js b/src/core/utils.js index a0d67e449a..55ac9be2a2 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -1,9 +1,9 @@ 'use strict' -const multihashes = require('multihashes') const promisify = require('promisify-es6') const map = require('async/map') const isIpfs = require('is-ipfs') +const CID = require('cids') exports.OFFLINE_ERROR = 'This command must be run in online mode. Try running \'ipfs daemon\' first.' @@ -61,12 +61,15 @@ const resolvePath = promisify(function (objectAPI, ipfsPaths, callback) { map(ipfsPaths, (path, cb) => { if (typeof path !== 'string') { + let cid + try { - multihashes.validate(path) + cid = new CID(path) } catch (err) { return cb(err) } - return cb(null, path) + + return cb(null, cid.buffer) } let parsedPath @@ -76,10 +79,10 @@ const resolvePath = promisify(function (objectAPI, ipfsPaths, callback) { return cb(err) } - const rootHash = multihashes.fromB58String(parsedPath.hash) + const rootHash = new CID(parsedPath.hash) const rootLinks = parsedPath.links if (!rootLinks.length) { - return cb(null, rootHash) + return cb(null, rootHash.buffer) } objectAPI.get(rootHash, follow.bind(null, rootLinks)) diff --git a/src/http/api/resources/files.js b/src/http/api/resources/files.js index a3b7e8eaed..0370c4b0c2 100644 --- a/src/http/api/resources/files.js +++ b/src/http/api/resources/files.js @@ -153,20 +153,8 @@ exports.add = { validate: { query: Joi.object() .keys({ - 'cid-version': Joi.number().integer().min(0).max(1), - // Temporary restriction on raw-leaves: - // When cid-version=1 then raw-leaves MUST be present and false. - // - // This is because raw-leaves is not yet implemented in js-ipfs, - // and go-ipfs changes the value of raw-leaves to true when - // cid-version > 0 unless explicitly set to false. - // - // This retains feature parity without having to implement raw-leaves. - 'raw-leaves': Joi.boolean().when('cid-version', { - is: 1, - then: Joi.boolean().valid(false).required(), - otherwise: Joi.boolean().valid(false) - }), + 'cid-version': Joi.number().integer().min(0).max(1).default(0), + 'raw-leaves': Joi.boolean(), 'only-hash': Joi.boolean(), pin: Joi.boolean().default(true), 'wrap-with-directory': Joi.boolean() diff --git a/test/cli/files.js b/test/cli/files.js index 2f749f1cfe..fda9c2de94 100644 --- a/test/cli/files.js +++ b/test/cli/files.js @@ -205,56 +205,34 @@ describe('files', () => runOnAndOff((thing) => { }) }) - // Temporarily expect to fail as raw-leaves not yet implemented. - // - // When cid-version=1 then raw-leaves MUST be present and false. - // - // This is because raw-leaves is not yet implemented in js-ipfs, - // and go-ipfs changes the value of raw-leaves to true when - // cid-version > 0 unless explicitly set to false. - // - // This retains feature parity without having to implement raw-leaves. it('add with cid-version=1', function () { this.timeout(30 * 1000) - return new Promise((resolve, reject) => { - ipfs('add src/init-files/init-docs/readme --cid-version=1') - .then(() => reject(new Error('Raw leaves not expected to be implemented'))) - .catch((err) => { - expect(err).to.exist() - resolve() - }) - }) + return ipfs('add src/init-files/init-docs/readme --cid-version=1') + .then((out) => { + expect(out) + .to.eql('added zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7 readme\n') + }) }) - // TODO: this test is failing, @alanshaw? - it.skip('add with cid-version=1 and raw-leaves=false', () => { - return ipfs('add src/init-files/init-docs/readme --cid-version=1 --raw-leaves=false').then((out) => { - expect(out) - .to.eql('added zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7 readme\n') - }) + it('add with cid-version=1 and raw-leaves=false', function () { + this.timeout(30 * 1000) + + return ipfs('add src/init-files/init-docs/readme --cid-version=1 --raw-leaves=false') + .then((out) => { + expect(out) + .to.eql('added zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7 readme\n') + }) }) - // Temporarily expect to fail as raw-leaves not yet implemented - // - // When cid-version=1 then raw-leaves MUST be present and false. - // - // This is because raw-leaves is not yet implemented in js-ipfs, - // and go-ipfs changes the value of raw-leaves to true when - // cid-version > 0 unless explicitly set to false. - // - // This retains feature parity without having to implement raw-leaves. it('add with cid-version=1 and raw-leaves=true', function () { this.timeout(30 * 1000) - return new Promise((resolve, reject) => { - ipfs('add src/init-files/init-docs/readme --cid-version=1 --raw-leaves=true') - .then(() => reject(new Error('Raw leaves not expected to be implemented'))) - .catch((err) => { - expect(err).to.exist() - resolve() - }) - }) + return ipfs('add src/init-files/init-docs/readme --cid-version=1 --raw-leaves=true') + .then((out) => { + expect(out) + .to.eql('added zdj7WiLc855B1KPRgV7Fh8ivjuAhePE1tuJafmxH5HmmSjqaD readme\n') + }) }) it('add --quiet', function () { From 45b80a0d2d45f34873d7b53d3caf82d1fd7c3fb2 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 27 Jul 2018 13:58:06 +0100 Subject: [PATCH 02/51] fix: emit boot error only once (#1472) `init` and `start` both emit any error they encounter. `boot` will then receive that error and also emit it! This PR adds an `emitted` property in `boot` to errors that came from `init` or `start` so that later in the code it knows whether to emit it or not! License: MIT Signed-off-by: Alan Shaw --- src/core/boot.js | 21 +++++++++++++++++---- test/core/create-node.spec.js | 24 +++++++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/core/boot.js b/src/core/boot.js index 0602cb90a0..50b2677b5c 100644 --- a/src/core/boot.js +++ b/src/core/boot.js @@ -30,7 +30,10 @@ module.exports = (self) => { (repoOpened, cb) => { // Init with existing initialized, opened, repo if (repoOpened) { - return self.init({ repo: self._repo }, (err) => cb(err)) + return self.init({ repo: self._repo }, (err) => { + if (err) return cb(Object.assign(err, { emitted: true })) + cb() + }) } if (doInit) { @@ -38,7 +41,10 @@ module.exports = (self) => { { bits: 2048, pass: self._options.pass }, typeof options.init === 'object' ? options.init : {} ) - return self.init(initOptions, (err) => cb(err)) + return self.init(initOptions, (err) => { + if (err) return cb(Object.assign(err, { emitted: true })) + cb() + }) } cb() @@ -48,11 +54,18 @@ module.exports = (self) => { if (!doStart) { return cb() } - self.start(cb) + + self.start((err) => { + if (err) return cb(Object.assign(err, { emitted: true })) + cb() + }) } ], (err) => { if (err) { - return self.emit('error', err) + if (!err.emitted) { + self.emit('error', err) + } + return } self.log('booted') self.emit('ready') diff --git a/test/core/create-node.spec.js b/test/core/create-node.spec.js index 5dcb86ace2..1d4b43ab2f 100644 --- a/test/core/create-node.spec.js +++ b/test/core/create-node.spec.js @@ -123,7 +123,7 @@ describe('create node', function () { }) }) - it('init: false errors (start default: true)', function (done) { + it('init: false errors (start default: true) and errors only once', function (done) { this.timeout(80 * 1000) const node = new IPFS({ @@ -135,10 +135,24 @@ describe('create node', function () { } } }) - node.once('error', (err) => { - expect(err).to.exist() - done() - }) + + const shouldHappenOnce = () => { + let timeoutId = null + + return (err) => { + expect(err).to.exist() + + // Bad news, this handler has been executed before + if (timeoutId) { + clearTimeout(timeoutId) + return done(new Error('error handler called multiple times')) + } + + timeoutId = setTimeout(done, 100) + } + } + + node.on('error', shouldHappenOnce()) }) it('init: false, start: false', function (done) { From bffe080cd6c3fa202ca0437f8abe38dca75e5903 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 27 Jul 2018 15:04:00 +0100 Subject: [PATCH 03/51] feat: preload content (#1464) refs #1459 This PR adds a new config property `preload`: ```js new IPFS({ preload: { enabled: false, addresses: ['/multiaddr/api/address'] } }) ``` * `preload.enabled` (default `false`) enables/disabled preloading - **should the default be false?** * `preload.addresses` array of node API addresses to preload content on. This are the addresses we make a `/api/v0/refs?arg=QmHash` request to, to initiate the preload **This PR upgrades the following APIs to preload content**. After adding content with `ipfs.files.add` (for example), we make a request to the first preload gateway addresses (providing `preload.enabled` is true), and will fall back to the second etc. * [x] `dag.put` * [x] `block.put` * [x] `object.new` * [x] `object.put` * [x] `object.patch.*` * [x] `mfs.*` MFS preloading is slightly different - we periodically submit your MFS root to the preload nodes when it changes. NOTE: this PR adds an option to `dag`, `block` and `object` APIs allowing users to opt out of preloading by specifying `preload: false` in their options object. License: MIT Signed-off-by: Alan Shaw --- .aegir.js | 31 ++++- README.md | 3 + package.json | 2 + src/core/components/block.js | 5 + src/core/components/dag.js | 10 +- src/core/components/files.js | 15 ++ src/core/components/index.js | 2 +- src/core/components/mfs.js | 24 ++++ src/core/components/object.js | 48 +++++-- src/core/components/pin-set.js | 5 +- src/core/components/pin.js | 4 +- src/core/components/start.js | 4 +- src/core/components/stop.js | 2 + src/core/config.js | 4 + src/core/index.js | 15 +- src/core/mfs-preload.js | 51 +++++++ src/core/preload.js | 88 ++++++++++++ src/core/runtime/config-browser.js | 4 +- src/core/runtime/config-nodejs.js | 4 +- src/core/runtime/preload-browser.js | 37 +++++ src/core/runtime/preload-nodejs.js | 64 +++++++++ test/cli/bootstrap.js | 6 +- test/core/bootstrap.spec.js | 6 +- test/core/mfs-preload.spec.js | 54 ++++++++ test/core/preload.spec.js | 203 ++++++++++++++++++++++++++++ test/fixtures/go-ipfs-repo/config | 6 +- test/utils/mock-preload-node.js | 157 +++++++++++++++++++++ 27 files changed, 824 insertions(+), 30 deletions(-) create mode 100644 src/core/components/mfs.js create mode 100644 src/core/mfs-preload.js create mode 100644 src/core/preload.js create mode 100644 src/core/runtime/preload-browser.js create mode 100644 src/core/runtime/preload-nodejs.js create mode 100644 test/core/mfs-preload.spec.js create mode 100644 test/core/preload.spec.js create mode 100644 test/utils/mock-preload-node.js diff --git a/.aegir.js b/.aegir.js index 979ffde3df..5d87aa66d7 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,8 +1,11 @@ 'use strict' -const createServer = require('ipfsd-ctl').createServer +const IPFSFactory = require('ipfsd-ctl') +const parallel = require('async/parallel') +const MockPreloadNode = require('./test/utils/mock-preload-node') -const server = createServer() +const ipfsdServer = IPFSFactory.createServer() +const preloadNode = MockPreloadNode.createNode() module.exports = { webpack: { @@ -21,9 +24,29 @@ module.exports = { singleRun: true }, hooks: { + node: { + pre: (cb) => preloadNode.start(cb), + post: (cb) => preloadNode.stop(cb) + }, browser: { - pre: server.start.bind(server), - post: server.stop.bind(server) + pre: (cb) => { + parallel([ + (cb) => { + ipfsdServer.start() + cb() + }, + (cb) => preloadNode.start(cb) + ], cb) + }, + post: (cb) => { + parallel([ + (cb) => { + ipfsdServer.stop() + cb() + }, + (cb) => preloadNode.stop(cb) + ], cb) + } } } } diff --git a/README.md b/README.md index 0c20f35cdb..c2dbefa5b3 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,9 @@ Creates and returns an instance of an IPFS node. Use the `options` argument to s - `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`) - `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`) +- `preload` (object): Configure external nodes that will preload content added to this node + - `enabled` (boolean): Enable content preloading (Default: `true`) + - `addresses` (array): Multiaddr API addresses of nodes that should preload content. NOTE: nodes specified here should also be added to your node's bootstrap address list at `config.Boostrap` - `EXPERIMENTAL` (object): Enable and configure experimental features. - `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`) - `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`) diff --git a/package.json b/package.json index 2cd7bcb477..e98cd5a278 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "./src/core/components/init-assets.js": false, "./src/core/runtime/config-nodejs.js": "./src/core/runtime/config-browser.js", "./src/core/runtime/libp2p-nodejs.js": "./src/core/runtime/libp2p-browser.js", + "./src/core/runtime/preload-nodejs.js": "./src/core/runtime/preload-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", @@ -140,6 +141,7 @@ "mime-types": "^2.1.18", "mkdirp": "~0.5.1", "multiaddr": "^5.0.0", + "multiaddr-to-uri": "^4.0.0", "multibase": "~0.4.0", "multihashes": "~0.4.13", "once": "^1.4.0", diff --git a/src/core/components/block.js b/src/core/components/block.js index 89b18db7d3..b68140af36 100644 --- a/src/core/components/block.js +++ b/src/core/components/block.js @@ -52,6 +52,11 @@ module.exports = function block (self) { if (err) { return cb(err) } + + if (options.preload !== false) { + self._preload(block.cid) + } + cb(null, block) }) ], callback) diff --git a/src/core/components/dag.js b/src/core/components/dag.js index 88a80bdcce..3aa67d1e88 100644 --- a/src/core/components/dag.js +++ b/src/core/components/dag.js @@ -24,7 +24,15 @@ module.exports = function dag (self) { options = options.cid ? options : Object.assign({}, optionDefaults, options) - self._ipld.put(dagNode, options, callback) + self._ipld.put(dagNode, options, (err, cid) => { + if (err) return callback(err) + + if (options.preload !== false) { + self._preload(cid) + } + + callback(null, cid) + }) }), get: promisify((cid, path, options, callback) => { diff --git a/src/core/components/files.js b/src/core/components/files.js index f69868bd77..12a1fcdce0 100644 --- a/src/core/components/files.js +++ b/src/core/components/files.js @@ -89,6 +89,20 @@ function normalizeContent (opts, content) { }) } +function preloadFile (self, opts, file) { + const isRootFile = opts.wrapWithDirectory + ? file.path === '' + : !file.path.includes('/') + + const shouldPreload = isRootFile && !opts.onlyHash && opts.preload !== false + + if (shouldPreload) { + self._preload(file.hash) + } + + return file +} + function pinFile (self, opts, file, cb) { // Pin a file if it is the root dir of a recursive add or the single file // of a direct add. @@ -158,6 +172,7 @@ module.exports = function files (self) { pull.flatten(), importer(self._ipld, opts), pull.asyncMap(prepareFile.bind(null, self, opts)), + pull.map(preloadFile.bind(null, self, opts)), pull.asyncMap(pinFile.bind(null, self, opts)) ) } diff --git a/src/core/components/index.js b/src/core/components/index.js index 9eb36ad4c3..1f6f084dee 100644 --- a/src/core/components/index.js +++ b/src/core/components/index.js @@ -26,4 +26,4 @@ exports.dht = require('./dht') exports.dns = require('./dns') exports.key = require('./key') exports.stats = require('./stats') -exports.mfs = require('ipfs-mfs/core') +exports.mfs = require('./mfs') diff --git a/src/core/components/mfs.js b/src/core/components/mfs.js new file mode 100644 index 0000000000..9f033545b4 --- /dev/null +++ b/src/core/components/mfs.js @@ -0,0 +1,24 @@ +'use strict' + +const promisify = require('promisify-es6') +const mfs = require('ipfs-mfs/core') + +module.exports = self => { + const mfsSelf = Object.assign({}, self) + + // A patched dag API to ensure preload doesn't happen for MFS operations + mfsSelf.dag = Object.assign({}, self.dag, { + put: promisify((node, opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = Object.assign({}, opts, { preload: false }) + + return self.dag.put(node, opts, cb) + }) + }) + + return mfs(mfsSelf, mfsSelf._options) +} diff --git a/src/core/components/object.js b/src/core/components/object.js index 5e8be00e4a..e7ca331056 100644 --- a/src/core/components/object.js +++ b/src/core/components/object.js @@ -80,10 +80,17 @@ module.exports = function object (self) { if (err) { return cb(err) } - self._ipld.put(node, { - cid: new CID(node.multihash) - }, (err) => { - cb(err, node) + + const cid = new CID(node.multihash) + + self._ipld.put(node, { cid }, (err) => { + if (err) return cb(err) + + if (options.preload !== false) { + self._preload(cid) + } + + cb(null, node) }) }) } @@ -92,12 +99,20 @@ module.exports = function object (self) { } return { - new: promisify((template, callback) => { + new: promisify((template, options, callback) => { if (typeof template === 'function') { callback = template template = undefined + options = {} + } + + if (typeof options === 'function') { + callback = options + options = {} } + options = options || {} + let data if (template) { @@ -111,13 +126,18 @@ module.exports = function object (self) { if (err) { return callback(err) } - self._ipld.put(node, { - cid: new CID(node.multihash) - }, (err) => { + + const cid = new CID(node.multihash) + + self._ipld.put(node, { cid }, (err) => { if (err) { return callback(err) } + if (options.preload !== false) { + self._preload(cid) + } + callback(null, node) }) }) @@ -166,13 +186,17 @@ module.exports = function object (self) { } function next () { - self._ipld.put(node, { - cid: new CID(node.multihash) - }, (err) => { + const cid = new CID(node.multihash) + + self._ipld.put(node, { cid }, (err) => { if (err) { return callback(err) } + if (options.preload !== false) { + self._preload(cid) + } + self.object.get(node.multihash, callback) }) } @@ -282,6 +306,8 @@ module.exports = function object (self) { editAndSave((node, cb) => { if (DAGLink.isDAGLink(linkRef)) { linkRef = linkRef._name + } else if (linkRef && linkRef.name) { + linkRef = linkRef.name } DAGNode.rmLink(node, linkRef, cb) })(multihash, options, callback) diff --git a/src/core/components/pin-set.js b/src/core/components/pin-set.js index f18a248604..806df5f05f 100644 --- a/src/core/components/pin-set.js +++ b/src/core/components/pin-set.js @@ -90,7 +90,7 @@ exports = module.exports = function (dag) { pinSet.storeItems(pins, (err, rootNode) => { if (err) { return callback(err) } - const opts = { cid: new CID(rootNode.multihash) } + const opts = { cid: new CID(rootNode.multihash), preload: false } dag.put(rootNode, opts, (err, cid) => { if (err) { return callback(err) } callback(null, rootNode) @@ -168,7 +168,8 @@ exports = module.exports = function (dag) { function storeChild (err, child, binIdx, cb) { if (err) { return cb(err) } - dag.put(child, { cid: new CID(child._multihash) }, err => { + const opts = { cid: new CID(child._multihash), preload: false } + dag.put(child, opts, err => { if (err) { return cb(err) } fanoutLinks[binIdx] = new DAGLink('', child.size, child.multihash) cb(null) diff --git a/src/core/components/pin.js b/src/core/components/pin.js index d2bd670e14..ce0cd72d84 100644 --- a/src/core/components/pin.js +++ b/src/core/components/pin.js @@ -80,14 +80,14 @@ module.exports = (self) => { // the pin-set nodes link to a special 'empty' node, so make sure it exists cb => DAGNode.create(Buffer.alloc(0), (err, empty) => { if (err) { return cb(err) } - dag.put(empty, { cid: new CID(empty.multihash) }, cb) + dag.put(empty, { cid: new CID(empty.multihash), preload: false }, cb) }), // create a root node with DAGLinks to the direct and recursive DAGs cb => DAGNode.create(Buffer.alloc(0), [dLink, rLink], (err, node) => { if (err) { return cb(err) } root = node - dag.put(root, { cid: new CID(root.multihash) }, cb) + dag.put(root, { cid: new CID(root.multihash), preload: false }, cb) }), // hack for CLI tests diff --git a/src/core/components/start.js b/src/core/components/start.js index fd4832e35a..3a7a5716ce 100644 --- a/src/core/components/start.js +++ b/src/core/components/start.js @@ -42,7 +42,9 @@ module.exports = (self) => { self._bitswap.start() self._blockService.setExchange(self._bitswap) - cb() + + self._preload.start() + self._mfsPreload.start(cb) } ], done) }) diff --git a/src/core/components/stop.js b/src/core/components/stop.js index 4d35190d21..cf97b6ec6a 100644 --- a/src/core/components/stop.js +++ b/src/core/components/stop.js @@ -30,8 +30,10 @@ module.exports = (self) => { self.state.stop() self._blockService.unsetExchange() self._bitswap.stop() + self._preload.stop() series([ + (cb) => self._mfsPreload.stop(cb), (cb) => self.libp2p.stop(cb), (cb) => self._repo.close(cb) ], done) diff --git a/src/core/config.js b/src/core/config.js index 1b04d10a2f..7b16c17d06 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -8,6 +8,10 @@ const schema = Joi.object().keys({ Joi.string() ).allow(null), repoOwner: Joi.boolean().default(true), + preload: Joi.object().keys({ + enabled: Joi.boolean().default(true), + addresses: Joi.array().items(Joi.multiaddr().options({ convert: false })) + }).allow(null), init: Joi.alternatives().try( Joi.boolean(), Joi.object().keys({ bits: Joi.number().integer() }) diff --git a/src/core/index.js b/src/core/index.js index 36f7c3a118..52266b7b41 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -22,6 +22,8 @@ const boot = require('./boot') const components = require('./components') // replaced by repo-browser when running in the browser const defaultRepo = require('./runtime/repo-nodejs') +const preload = require('./preload') +const mfsPreload = require('./mfs-preload') class IPFS extends EventEmitter { constructor (options) { @@ -30,7 +32,14 @@ class IPFS extends EventEmitter { this._options = { init: true, start: true, - EXPERIMENTAL: {} + EXPERIMENTAL: {}, + preload: { + enabled: true, + addresses: [ + '/dnsaddr/node0.preload.ipfs.io/https', + '/dnsaddr/node1.preload.ipfs.io/https' + ] + } } options = config.validate(options || {}) @@ -78,6 +87,8 @@ class IPFS extends EventEmitter { this._blockService = new BlockService(this._repo) this._ipld = new Ipld(this._blockService) this._pubsub = undefined + this._preload = preload(this) + this._mfsPreload = mfsPreload(this) // IPFS Core exposed components // - for booting up a node @@ -134,7 +145,7 @@ class IPFS extends EventEmitter { } // ipfs.files - const mfs = components.mfs(this, this._options) + const mfs = components.mfs(this) Object.keys(mfs).forEach(key => { this.files[key] = mfs[key] diff --git a/src/core/mfs-preload.js b/src/core/mfs-preload.js new file mode 100644 index 0000000000..4f0cfd16a5 --- /dev/null +++ b/src/core/mfs-preload.js @@ -0,0 +1,51 @@ +'use strict' + +const debug = require('debug') + +const log = debug('jsipfs:mfs-preload') +log.error = debug('jsipfs:mfs-preload:error') + +module.exports = (self, options) => { + options = options || {} + options.interval = options.interval || 30 * 1000 + + let rootCid + let timeoutId + + const preloadMfs = () => { + self.files.stat('/', (err, stats) => { + if (err) { + timeoutId = setTimeout(preloadMfs, options.interval) + return log.error('failed to stat MFS root for preload', err) + } + + if (rootCid !== stats.hash) { + log(`preloading updated MFS root ${rootCid} -> ${stats.hash}`) + + return self._preload(stats.hash, (err) => { + timeoutId = setTimeout(preloadMfs, options.interval) + if (err) return log.error(`failed to preload MFS root ${stats.hash}`, err) + rootCid = stats.hash + }) + } + + timeoutId = setTimeout(preloadMfs, options.interval) + }) + } + + return { + start (cb) { + self.files.stat('/', (err, stats) => { + if (err) return cb(err) + rootCid = stats.hash + log(`monitoring MFS root ${rootCid}`) + timeoutId = setTimeout(preloadMfs, options.interval) + cb() + }) + }, + stop (cb) { + clearTimeout(timeoutId) + cb() + } + } +} diff --git a/src/core/preload.js b/src/core/preload.js new file mode 100644 index 0000000000..d99a9d8f20 --- /dev/null +++ b/src/core/preload.js @@ -0,0 +1,88 @@ +'use strict' + +const setImmediate = require('async/setImmediate') +const retry = require('async/retry') +const toUri = require('multiaddr-to-uri') +const debug = require('debug') +const CID = require('cids') +const preload = require('./runtime/preload-nodejs') + +const log = debug('jsipfs:preload') +log.error = debug('jsipfs:preload:error') + +const noop = (err) => { if (err) log.error(err) } + +module.exports = self => { + const options = self._options.preload || {} + options.enabled = Boolean(options.enabled) + options.addresses = options.addresses || [] + + if (!options.enabled || !options.addresses.length) { + return (_, callback) => { + if (callback) { + setImmediate(() => callback()) + } + } + } + + let stopped = true + let requests = [] + const apiUris = options.addresses.map(apiAddrToUri) + + const api = (cid, callback) => { + callback = callback || noop + + if (typeof cid !== 'string') { + try { + cid = new CID(cid).toBaseEncodedString() + } catch (err) { + return setImmediate(() => callback(err)) + } + } + + const fallbackApiUris = Array.from(apiUris) + let request + const now = Date.now() + + retry({ times: fallbackApiUris.length }, (cb) => { + if (stopped) return cb(new Error(`preload aborted for ${cid}`)) + + // Remove failed request from a previous attempt + requests = requests.filter(r => r !== request) + + const apiUri = fallbackApiUris.shift() + + request = preload(`${apiUri}/api/v0/refs?r=true&arg=${cid}`, cb) + requests = requests.concat(request) + }, (err) => { + requests = requests.filter(r => r !== request) + + if (err) { + return callback(err) + } + + log(`preloaded ${cid} in ${Date.now() - now}ms`) + callback() + }) + } + + api.start = () => { + stopped = false + } + + api.stop = () => { + stopped = true + log(`canceling ${requests.length} pending preload request(s)`) + requests.forEach(r => r.cancel()) + requests = [] + } + + return api +} + +function apiAddrToUri (addr) { + if (!(addr.endsWith('http') || addr.endsWith('https'))) { + addr = addr + '/http' + } + return toUri(addr) +} diff --git a/src/core/runtime/config-browser.js b/src/core/runtime/config-browser.js index 9819c04aaa..f7662420cb 100644 --- a/src/core/runtime/config-browser.js +++ b/src/core/runtime/config-browser.js @@ -24,6 +24,8 @@ module.exports = () => ({ '/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm', '/dns4/nyc-2.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64', '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' + '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', + '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', + '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] }) diff --git a/src/core/runtime/config-nodejs.js b/src/core/runtime/config-nodejs.js index 995f66261d..d56550e181 100644 --- a/src/core/runtime/config-nodejs.js +++ b/src/core/runtime/config-nodejs.js @@ -37,6 +37,8 @@ module.exports = () => ({ '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' + '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', + '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', + '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] }) diff --git a/src/core/runtime/preload-browser.js b/src/core/runtime/preload-browser.js new file mode 100644 index 0000000000..8d123a12be --- /dev/null +++ b/src/core/runtime/preload-browser.js @@ -0,0 +1,37 @@ +/* eslint-env browser */ +'use strict' + +const debug = require('debug') + +const log = debug('jsipfs:preload') +log.error = debug('jsipfs:preload:error') + +module.exports = function preload (url, callback) { + log(url) + + const req = new self.XMLHttpRequest() + + req.open('HEAD', url) + + req.onreadystatechange = function () { + if (this.readyState !== this.DONE) { + return + } + + if (this.status < 200 || this.status >= 300) { + log.error('failed to preload', url, this.status, this.statusText) + return callback(new Error(`failed to preload ${url}`)) + } + + callback() + } + + req.send() + + return { + cancel: () => { + req.abort() + callback(new Error('request aborted')) + } + } +} diff --git a/src/core/runtime/preload-nodejs.js b/src/core/runtime/preload-nodejs.js new file mode 100644 index 0000000000..405798ca34 --- /dev/null +++ b/src/core/runtime/preload-nodejs.js @@ -0,0 +1,64 @@ +'use strict' + +const http = require('http') +const https = require('https') +const { URL } = require('url') +const debug = require('debug') +const setImmediate = require('async/setImmediate') + +const log = debug('jsipfs:preload') +log.error = debug('jsipfs:preload:error') + +module.exports = function preload (url, callback) { + log(url) + + try { + url = new URL(url) + } catch (err) { + return setImmediate(() => callback(err)) + } + + const transport = url.protocol === 'https:' ? https : http + + const req = transport.get({ + hostname: url.hostname, + port: url.port, + path: url.pathname + url.search + }, (res) => { + if (res.statusCode < 200 || res.statusCode >= 300) { + res.resume() + log.error('failed to preload', url.href, res.statusCode, res.statusMessage) + return callback(new Error(`failed to preload ${url}`)) + } + + res.on('data', chunk => log(`data ${chunk}`)) + + res.on('abort', () => { + callback(new Error('request aborted')) + }) + + res.on('error', err => { + log.error('response error preloading', url.href, err) + callback(err) + }) + + res.on('end', () => { + // If aborted, callback is called in the abort handler + if (!res.aborted) callback() + }) + }) + + req.on('error', err => { + log.error('request error preloading', url.href, err) + callback(err) + }) + + return { + cancel: () => { + // No need to call callback here + // before repsonse - called in req error handler + // after response - called in res abort hander + req.abort() + } + } +} diff --git a/test/cli/bootstrap.js b/test/cli/bootstrap.js index 8807a12411..a301bca3f2 100644 --- a/test/cli/bootstrap.js +++ b/test/cli/bootstrap.js @@ -31,7 +31,9 @@ describe('bootstrap', () => runOnAndOff((thing) => { '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' + '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', + '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', + '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] const updatedList = [ @@ -54,6 +56,8 @@ describe('bootstrap', () => runOnAndOff((thing) => { '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', + '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', + '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/ip4/111.111.111.111/tcp/1001/ipfs/QmcyFFKfLDGJKwufn2GeitxvhricsBQyNKTkrD14psikoD' ] diff --git a/test/core/bootstrap.spec.js b/test/core/bootstrap.spec.js index f092765354..d95d622f18 100644 --- a/test/core/bootstrap.spec.js +++ b/test/core/bootstrap.spec.js @@ -59,7 +59,9 @@ describe('bootstrap', () => { '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' + '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', + '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', + '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] const updatedList = [ @@ -82,6 +84,8 @@ describe('bootstrap', () => { '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', + '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', + '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/ip4/111.111.111.111/tcp/1001/ipfs/QmXFX2P5ammdmXQgfqGkfswtEVFsZUJ5KeHRXQYCTdiTAb' ] diff --git a/test/core/mfs-preload.spec.js b/test/core/mfs-preload.spec.js new file mode 100644 index 0000000000..98d1fb70ed --- /dev/null +++ b/test/core/mfs-preload.spec.js @@ -0,0 +1,54 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const mfsPreload = require('../../src/core/mfs-preload') + +const createMockFilesStat = (cids = []) => { + let n = 0 + return (path, cb) => cb(null, { hash: cids[n++] || 'QmHash' }) +} + +const createMockPreload = () => { + return function preload (cid, cb) { + preload.cids = preload.cids || [] + preload.cids.push(cid) + cb() + } +} + +describe('MFS preload', () => { + it('should preload MFS root periodically', (done) => { + // CIDs returned from our mock files.stat function + const statCids = ['QmInitial', 'QmSame', 'QmSame', 'QmUpdated'] + // The CIDs we expect to have been preloaded + const expectedPreloadCids = ['QmSame', 'QmUpdated'] + + const mockPreload = createMockPreload() + const mockFilesStat = createMockFilesStat(statCids) + const mockIpfs = { files: { stat: mockFilesStat }, _preload: mockPreload } + + const interval = 10 + const preloader = mfsPreload(mockIpfs, { interval }) + + preloader.start((err) => { + expect(err).to.not.exist() + + setTimeout(() => { + preloader.stop((err) => { + expect(err).to.not.exist() + expect( + // Slice off any extra CIDs it processed + mockPreload.cids.slice(0, expectedPreloadCids.length) + ).to.deep.equal(expectedPreloadCids) + done() + }) + }, statCids.length * (interval + 5)) + }) + }) +}) diff --git a/test/core/preload.spec.js b/test/core/preload.spec.js new file mode 100644 index 0000000000..38db0f3412 --- /dev/null +++ b/test/core/preload.spec.js @@ -0,0 +1,203 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const CID = require('cids') +const parallel = require('async/parallel') +const waterfall = require('async/waterfall') +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const MockPreloadNode = require('../utils/mock-preload-node') +const IPFS = require('../../src') + +describe('preload', () => { + let ipfs + + before((done) => { + ipfs = new IPFS({ + config: { + Addresses: { + Swarm: [] + } + }, + preload: { + enabled: true, + addresses: [MockPreloadNode.defaultAddr] + } + }) + + ipfs.on('ready', done) + }) + + afterEach((done) => MockPreloadNode.clearPreloadCids(done)) + + after((done) => ipfs.stop(done)) + + it('should preload content added with files.add', (done) => { + ipfs.files.add(Buffer.from(hat()), (err, res) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(res[0].hash, done) + }) + }) + + it('should preload multiple content added with files.add', (done) => { + ipfs.files.add([{ + content: Buffer.from(hat()) + }, { + content: Buffer.from(hat()) + }, { + content: Buffer.from(hat()) + }], (err, res) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(res.map(file => file.hash), done) + }) + }) + + it('should preload multiple content and intermediate dirs added with files.add', (done) => { + ipfs.files.add([{ + path: 'dir0/dir1/file0', + content: Buffer.from(hat()) + }, { + path: 'dir0/dir1/file1', + content: Buffer.from(hat()) + }, { + path: 'dir0/file2', + content: Buffer.from(hat()) + }], (err, res) => { + expect(err).to.not.exist() + + const rootDir = res.find(file => file.path === 'dir0') + expect(rootDir).to.exist() + + MockPreloadNode.waitForCids(rootDir.hash, done) + }) + }) + + it('should preload multiple content and wrapping dir for content added with files.add and wrapWithDirectory option', (done) => { + ipfs.files.add([{ + path: 'dir0/dir1/file0', + content: Buffer.from(hat()) + }, { + path: 'dir0/dir1/file1', + content: Buffer.from(hat()) + }, { + path: 'dir0/file2', + content: Buffer.from(hat()) + }], { wrapWithDirectory: true }, (err, res) => { + expect(err).to.not.exist() + + const wrappingDir = res.find(file => file.path === '') + expect(wrappingDir).to.exist() + + MockPreloadNode.waitForCids(wrappingDir.hash, done) + }) + }) + + it('should preload content added with object.new', (done) => { + ipfs.object.new((err, node) => { + expect(err).to.not.exist() + + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + + it('should preload content added with object.put', (done) => { + ipfs.object.put({ Data: Buffer.from(hat()), Links: [] }, (err, node) => { + expect(err).to.not.exist() + + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + + it('should preload content added with object.patch.addLink', (done) => { + parallel({ + parent: (cb) => ipfs.object.put({ Data: Buffer.from(hat()), Links: [] }, cb), + link: (cb) => ipfs.object.put({ Data: Buffer.from(hat()), Links: [] }, cb) + }, (err, nodes) => { + expect(err).to.not.exist() + + ipfs.object.patch.addLink(nodes.parent.multihash, { + name: 'link', + multihash: nodes.link.multihash, + size: nodes.link.size + }, (err, node) => { + expect(err).to.not.exist() + + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + }) + + it('should preload content added with object.patch.rmLink', (done) => { + waterfall([ + (cb) => ipfs.object.put({ Data: Buffer.from(hat()), Links: [] }, cb), + (link, cb) => { + ipfs.object.put({ + Data: Buffer.from(hat()), + Links: [{ + name: 'link', + multihash: link.multihash, + size: link.size + }] + }, cb) + } + ], (err, parent) => { + expect(err).to.not.exist() + + ipfs.object.patch.rmLink(parent.multihash, { name: 'link' }, (err, node) => { + expect(err).to.not.exist() + + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + }) + + it('should preload content added with object.patch.setData', (done) => { + ipfs.object.put({ Data: Buffer.from(hat()), Links: [] }, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.patch.setData(node.multihash, Buffer.from(hat()), (err, node) => { + expect(err).to.not.exist() + + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + }) + + it('should preload content added with object.patch.appendData', (done) => { + ipfs.object.put({ Data: Buffer.from(hat()), Links: [] }, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.patch.appendData(node.multihash, Buffer.from(hat()), (err, node) => { + expect(err).to.not.exist() + + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + }) + + it('should preload content added with block.put', (done) => { + ipfs.block.put(Buffer.from(hat()), (err, block) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(block.cid.toBaseEncodedString(), done) + }) + }) + + it('should preload content added with dag.put', (done) => { + const obj = { test: hat() } + ipfs.dag.put(obj, { format: 'dag-cbor', hashAlg: 'sha2-256' }, (err, cid) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) +}) diff --git a/test/fixtures/go-ipfs-repo/config b/test/fixtures/go-ipfs-repo/config index 00f467f95f..9843d866a8 100644 --- a/test/fixtures/go-ipfs-repo/config +++ b/test/fixtures/go-ipfs-repo/config @@ -65,7 +65,9 @@ "/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3", "/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx", "/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic", - "/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6" + "/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6", + "/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic", + "/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6" ], "Tour": { "Last": "" @@ -106,4 +108,4 @@ "hash": "sha2-512" } } -} \ No newline at end of file +} diff --git a/test/utils/mock-preload-node.js b/test/utils/mock-preload-node.js new file mode 100644 index 0000000000..370847bf72 --- /dev/null +++ b/test/utils/mock-preload-node.js @@ -0,0 +1,157 @@ +/* eslint-env browser */ +'use strict' + +const http = require('http') +const toUri = require('multiaddr-to-uri') +const URL = require('url').URL || self.URL + +const defaultPort = 1138 +const defaultAddr = `/dnsaddr/localhost/tcp/${defaultPort}` + +module.exports.defaultAddr = defaultAddr + +// Create a mock preload IPFS node with a gateway that'll respond 200 to a +// request for /api/v0/refs?arg=*. It remembers the preload CIDs it has been +// called with, and you can ask it for them and also clear them by issuing a +// GET/DELETE request to /cids. +module.exports.createNode = () => { + let cids = [] + + const server = http.createServer((req, res) => { + if (req.url.startsWith('/api/v0/refs')) { + const arg = new URL(`https://ipfs.io${req.url}`).searchParams.get('arg') + cids = cids.concat(arg) + } else if (req.method === 'DELETE' && req.url === '/cids') { + res.statusCode = 204 + cids = [] + } else if (req.method === 'GET' && req.url === '/cids') { + res.setHeader('Content-Type', 'application/json') + res.write(JSON.stringify(cids)) + } else { + res.statusCode = 500 + } + + res.end() + }) + + server.start = (opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + return server.listen(Object.assign({ port: defaultPort }, opts), cb) + } + + server.stop = (cb) => server.close(cb) + + return server +} + +function parseMultiaddr (addr) { + if (!(addr.endsWith('http') || addr.endsWith('https'))) { + addr = addr + '/http' + } + return new URL(toUri(addr)) +} + +// Get the stored preload CIDs for the server at `addr` +const getPreloadCids = (addr, cb) => { + if (typeof addr === 'function') { + cb = addr + addr = defaultAddr + } + + addr = addr || defaultAddr + + const { protocol, hostname, port } = parseMultiaddr(addr) + + const req = http.get({ protocol, hostname, port, path: '/cids' }, (res) => { + if (res.statusCode !== 200) { + res.resume() + return cb(new Error('failed to get preloaded CIDs from mock preload node')) + } + + let data = '' + + res.on('error', cb) + res.on('data', chunk => { data += chunk }) + + res.on('end', () => { + let obj + try { + obj = JSON.parse(data) + } catch (err) { + return cb(err) + } + cb(null, obj) + }) + }) + + req.on('error', cb) +} + +module.exports.getPreloadCids = getPreloadCids + +// Clear the stored preload URLs for the server at `addr` +module.exports.clearPreloadCids = (addr, cb) => { + if (typeof addr === 'function') { + cb = addr + addr = defaultAddr + } + + addr = addr || defaultAddr + + const { protocol, hostname, port } = parseMultiaddr(addr) + + const req = http.request({ + method: 'DELETE', + protocol, + hostname, + port, + path: '/cids' + }, (res) => { + res.resume() + + if (res.statusCode !== 204) { + return cb(new Error('failed to clear CIDs from mock preload node')) + } + + cb() + }) + + req.on('error', cb) + req.end() +} + +// Wait for the passed CIDs to appear in the CID list from the preload node +module.exports.waitForCids = (cids, opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = opts || {} + opts.timeout = opts.timeout || 1000 + + cids = Array.isArray(cids) ? cids : [cids] + + const start = Date.now() + + const checkForCid = () => { + getPreloadCids(opts.addr, (err, preloadCids) => { + if (err) return cb(err) + + if (cids.every(cid => preloadCids.includes(cid))) { + return cb() + } + + if (Date.now() > start + opts.timeout) { + return cb(new Error('Timed out waiting for CIDs to be preloaded')) + } + + setTimeout(checkForCid, 10) + }) + } + + checkForCid() +} From e3868f48d209f1985579b6be8b00beeccca0c319 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sat, 28 Jul 2018 09:18:06 +0200 Subject: [PATCH 04/51] feat: remove decomissioned bootstrappers --- src/core/runtime/config-browser.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/runtime/config-browser.js b/src/core/runtime/config-browser.js index f7662420cb..ca8c99e153 100644 --- a/src/core/runtime/config-browser.js +++ b/src/core/runtime/config-browser.js @@ -23,8 +23,6 @@ module.exports = () => ({ '/dns4/sgp-1.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu', '/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm', '/dns4/nyc-2.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64', - '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] From 90e9f68db1c74e05af7e6edaf9863bae3c012a72 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sat, 28 Jul 2018 09:18:32 +0200 Subject: [PATCH 05/51] feat: rm decomissioned bootstrappers - nodejs --- src/core/runtime/config-nodejs.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/runtime/config-nodejs.js b/src/core/runtime/config-nodejs.js index d56550e181..5b301d1e20 100644 --- a/src/core/runtime/config-nodejs.js +++ b/src/core/runtime/config-nodejs.js @@ -36,8 +36,6 @@ module.exports = () => ({ '/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', - '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] From 649b755c94456a356786397b4577ec659120ccbc Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 09:45:05 +0100 Subject: [PATCH 06/51] feat: preload on content fetch requests (#1475) When JS IPFS requests content not stored locally it needs to ask the peers it knows about to provide it if they have it. The peers are not relays so if they don't have it, they won't find it and provide it to JS IPFS. However, if we issue a preload request prior to these requests we prompt the preload nodes to fetch the content using their DHT and they can then provide it to JS IPFS. License: MIT Signed-off-by: Alan Shaw * chore: update dependencies (#1473) * chore: update libp2p and is-ipfs dependencies License: MIT Signed-off-by: Alan Shaw * chore: update all the deps * fix: rm non used boostrappers from tests * test: increase mfs preload timeout * test: pin-set timeout increase * test: add preload tests for content retrieval License: MIT Signed-off-by: Alan Shaw * fix: GET request not HEAD License: MIT Signed-off-by: Alan Shaw --- package.json | 24 ++++---- src/core/components/block.js | 25 ++++++++- src/core/components/dag.js | 19 ++++++- src/core/components/files.js | 58 ++++++++++++++++--- src/core/components/mfs.js | 16 ++++++ src/core/components/object.js | 10 +++- src/core/components/pin-set.js | 6 +- src/core/components/pin.js | 2 +- src/core/runtime/preload-browser.js | 2 +- test/cli/bootstrap.js | 4 -- test/core/bootstrap.spec.js | 4 -- test/core/mfs-preload.spec.js | 6 +- test/core/pin-set.js | 4 +- test/core/preload.spec.js | 86 +++++++++++++++++++++++++++++ test/fixtures/go-ipfs-repo/config | 2 - 15 files changed, 223 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index e98cd5a278..36460d3a92 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ }, "homepage": "https://github.com/ipfs/js-ipfs#readme", "devDependencies": { - "aegir": "^15.0.0", + "aegir": "^15.1.0", "buffer-loader": "~0.0.1", "chai": "^4.1.2", "delay": "^3.0.0", @@ -70,8 +70,8 @@ "expose-loader": "~0.7.5", "form-data": "^2.3.2", "hat": "0.0.3", - "interface-ipfs-core": "~0.72.0", - "ipfsd-ctl": "~0.37.5", + "interface-ipfs-core": "~0.72.1", + "ipfsd-ctl": "~0.38.0", "mocha": "^5.2.0", "ncp": "^2.0.0", "nexpect": "~0.5.0", @@ -93,7 +93,7 @@ "byteman": "^1.3.5", "cids": "~0.5.3", "debug": "^3.1.0", - "file-type": "^8.0.0", + "file-type": "^8.1.0", "filesize": "^3.6.1", "fnv1a": "^1.0.1", "fsm-event": "^2.1.0", @@ -104,30 +104,30 @@ "hoek": "^5.0.3", "human-to-milliseconds": "^1.0.0", "interface-datastore": "~0.4.2", - "ipfs-api": "^22.2.1", - "ipfs-bitswap": "~0.20.2", + "ipfs-api": "^22.2.4", + "ipfs-bitswap": "~0.20.3", "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", "ipfs-http-response": "~0.1.2", - "ipfs-mfs": "~0.2.2", + "ipfs-mfs": "~0.2.3", "ipfs-multipart": "~0.1.0", "ipfs-repo": "~0.22.1", "ipfs-unixfs": "~0.1.15", "ipfs-unixfs-engine": "~0.31.3", "ipld": "~0.17.3", "ipld-dag-cbor": "~0.12.1", - "ipld-dag-pb": "~0.14.5", - "is-ipfs": "~0.3.2", + "ipld-dag-pb": "~0.14.6", + "is-ipfs": "~0.4.2", "is-pull-stream": "~0.0.0", "is-stream": "^1.1.0", "joi": "^13.4.0", "joi-browser": "^13.4.0", "joi-multiaddr": "^2.0.0", - "libp2p": "~0.22.0", + "libp2p": "~0.23.0", "libp2p-bootstrap": "~0.9.3", "libp2p-circuit": "~0.2.0", "libp2p-floodsub": "~0.15.0", - "libp2p-kad-dht": "~0.10.0", + "libp2p-kad-dht": "~0.10.1", "libp2p-keychain": "~0.3.1", "libp2p-mdns": "~0.12.0", "libp2p-mplex": "~0.8.0", @@ -138,7 +138,7 @@ "libp2p-websockets": "~0.12.0", "lodash": "^4.17.10", "mafmt": "^6.0.0", - "mime-types": "^2.1.18", + "mime-types": "^2.1.19", "mkdirp": "~0.5.1", "multiaddr": "^5.0.0", "multiaddr-to-uri": "^4.0.0", diff --git a/src/core/components/block.js b/src/core/components/block.js index b68140af36..babb0e0752 100644 --- a/src/core/components/block.js +++ b/src/core/components/block.js @@ -9,8 +9,20 @@ const promisify = require('promisify-es6') module.exports = function block (self) { return { - get: promisify((cid, callback) => { + get: promisify((cid, options, callback) => { + if (typeof options === 'function') { + callback = options + options = {} + } + + options = options || {} + cid = cleanCid(cid) + + if (options.preload !== false) { + self._preload(cid) + } + self._blockService.get(cid, callback) }), put: promisify((block, options, callback) => { @@ -65,9 +77,18 @@ module.exports = function block (self) { cid = cleanCid(cid) self._blockService.delete(cid, callback) }), - stat: promisify((cid, callback) => { + stat: promisify((cid, options, callback) => { + if (typeof options === 'function') { + callback = options + options = {} + } + cid = cleanCid(cid) + if (options.preload !== false) { + self._preload(cid) + } + self._blockService.get(cid, (err, block) => { if (err) { return callback(err) diff --git a/src/core/components/dag.js b/src/core/components/dag.js index 3aa67d1e88..e8cecdb7a4 100644 --- a/src/core/components/dag.js +++ b/src/core/components/dag.js @@ -66,6 +66,10 @@ module.exports = function dag (self) { } } + if (options.preload !== false) { + self._preload(cid) + } + self._ipld.get(cid, path, options, callback) }), @@ -100,6 +104,10 @@ module.exports = function dag (self) { } } + if (options.preload !== false) { + self._preload(cid) + } + pull( self._ipld.treeStream(cid, path, options), pull.collect(callback) @@ -107,10 +115,17 @@ module.exports = function dag (self) { }), // TODO - use IPLD selectors once they are implemented - _getRecursive: promisify((multihash, callback) => { + _getRecursive: promisify((multihash, options, callback) => { // gets flat array of all DAGNodes in tree given by multihash - self.dag.get(new CID(multihash), (err, res) => { + if (typeof options === 'function') { + callback = options + options = {} + } + + options = options || {} + + self.dag.get(new CID(multihash), '', options, (err, res) => { if (err) { return callback(err) } mapAsync(res.value.links, (link, cb) => { diff --git a/src/core/components/files.js b/src/core/components/files.js index 12a1fcdce0..8877e19b77 100644 --- a/src/core/components/files.js +++ b/src/core/components/files.js @@ -182,11 +182,17 @@ module.exports = function files (self) { throw new Error('You must supply an ipfsPath') } + options = options || {} + ipfsPath = normalizePath(ipfsPath) const pathComponents = ipfsPath.split('/') const restPath = normalizePath(pathComponents.slice(1).join('/')) const filterFile = (file) => (restPath && file.path === restPath) || (file.path === ipfsPath) + if (options.preload !== false) { + self._preload(pathComponents[0]) + } + const d = deferred.source() pull( @@ -213,16 +219,21 @@ module.exports = function files (self) { } function _lsPullStreamImmutable (ipfsPath, options) { + options = options || {} + const path = normalizePath(ipfsPath) - const recursive = options && options.recursive - const pathDepth = path.split('/').length + const recursive = options.recursive + const pathComponents = path.split('/') + const pathDepth = pathComponents.length const maxDepth = recursive ? global.Infinity : pathDepth - const opts = Object.assign({}, { - maxDepth: maxDepth - }, options) + options.maxDepth = options.maxDepth || maxDepth + + if (options.preload !== false) { + self._preload(pathComponents[0]) + } return pull( - exporter(ipfsPath, self._ipld, opts), + exporter(ipfsPath, self._ipld, options), pull.filter(node => recursive ? node.depth >= pathDepth : node.depth === pathDepth ), @@ -334,8 +345,11 @@ module.exports = function files (self) { options = {} } - if (typeof callback !== 'function') { - throw new Error('Please supply a callback to ipfs.files.get') + options = options || {} + + if (options.preload !== false) { + const pathComponents = normalizePath(ipfsPath).split('/') + self._preload(pathComponents[0]) } pull( @@ -359,6 +373,13 @@ module.exports = function files (self) { }), getReadableStream: (ipfsPath, options) => { + options = options || {} + + if (options.preload !== false) { + const pathComponents = normalizePath(ipfsPath).split('/') + self._preload(pathComponents[0]) + } + return toStream.source( pull( exporter(ipfsPath, self._ipld, options), @@ -375,6 +396,13 @@ module.exports = function files (self) { }, getPullStream: (ipfsPath, options) => { + options = options || {} + + if (options.preload !== false) { + const pathComponents = normalizePath(ipfsPath).split('/') + self._preload(pathComponents[0]) + } + return exporter(ipfsPath, self._ipld, options) }, @@ -384,6 +412,13 @@ module.exports = function files (self) { options = {} } + options = options || {} + + if (options.preload !== false) { + const pathComponents = normalizePath(ipfsPath).split('/') + self._preload(pathComponents[0]) + } + pull( _lsPullStreamImmutable(ipfsPath, options), pull.collect((err, values) => { @@ -397,6 +432,13 @@ module.exports = function files (self) { }), lsReadableStreamImmutable: (ipfsPath, options) => { + options = options || {} + + if (options.preload !== false) { + const pathComponents = normalizePath(ipfsPath).split('/') + self._preload(pathComponents[0]) + } + return toStream.source(_lsPullStreamImmutable(ipfsPath, options)) }, diff --git a/src/core/components/mfs.js b/src/core/components/mfs.js index 9f033545b4..2fd44d2979 100644 --- a/src/core/components/mfs.js +++ b/src/core/components/mfs.js @@ -7,7 +7,23 @@ module.exports = self => { const mfsSelf = Object.assign({}, self) // A patched dag API to ensure preload doesn't happen for MFS operations + // (MFS is preloaded periodically) mfsSelf.dag = Object.assign({}, self.dag, { + get: promisify((cid, path, opts, cb) => { + if (typeof path === 'function') { + cb = path + path = undefined + } + + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = Object.assign({}, opts, { preload: false }) + + return self.dag.get(cid, path, opts, cb) + }), put: promisify((node, opts, cb) => { if (typeof opts === 'function') { cb = opts diff --git a/src/core/components/object.js b/src/core/components/object.js index e7ca331056..3b6012ed35 100644 --- a/src/core/components/object.js +++ b/src/core/components/object.js @@ -1,6 +1,7 @@ 'use strict' const waterfall = require('async/waterfall') +const setImmediate = require('async/setImmediate') const promisify = require('promisify-es6') const dagPB = require('ipld-dag-pb') const DAGNode = dagPB.DAGNode @@ -8,7 +9,6 @@ const DAGLink = dagPB.DAGLink const CID = require('cids') const mh = require('multihashes') const Unixfs = require('ipfs-unixfs') -const assert = require('assert') function normalizeMultihash (multihash, enc) { if (typeof multihash === 'string') { @@ -116,7 +116,9 @@ module.exports = function object (self) { let data if (template) { - assert(template === 'unixfs-dir', 'unkown template') + if (template !== 'unixfs-dir') { + return setImmediate(() => callback(new Error('unknown template'))) + } data = (new Unixfs('directory')).marshal() } else { data = Buffer.alloc(0) @@ -221,6 +223,10 @@ module.exports = function object (self) { cid = cid.toV1() } + if (options.preload !== false) { + self._preload(cid) + } + self._ipld.get(cid, (err, result) => { if (err) { return callback(err) diff --git a/src/core/components/pin-set.js b/src/core/components/pin-set.js index 806df5f05f..1c816d993c 100644 --- a/src/core/components/pin-set.js +++ b/src/core/components/pin-set.js @@ -74,7 +74,7 @@ exports = module.exports = function (dag) { seen[bs58Link] = true - dag.get(multihash, (err, res) => { + dag.get(multihash, '', { preload: false }, (err, res) => { if (err) { return someCb(err) } searchChildren(res.value, someCb) }) @@ -184,7 +184,7 @@ exports = module.exports = function (dag) { return callback(new Error('No link found with name ' + name)) } - dag.get(link.multihash, (err, res) => { + dag.get(link.multihash, '', { preload: false }, (err, res) => { if (err) { return callback(err) } const keys = [] const step = link => keys.push(link.multihash) @@ -211,7 +211,7 @@ exports = module.exports = function (dag) { if (!emptyKey.equals(linkHash)) { // walk the links of this fanout bin - return dag.get(linkHash, (err, res) => { + return dag.get(linkHash, '', { preload: false }, (err, res) => { if (err) { return eachCb(err) } pinSet.walkItems(res.value, step, eachCb) }) diff --git a/src/core/components/pin.js b/src/core/components/pin.js index ce0cd72d84..d1d7456fb6 100644 --- a/src/core/components/pin.js +++ b/src/core/components/pin.js @@ -360,7 +360,7 @@ module.exports = (self) => { (_, cb) => repo.datastore.has(pinDataStoreKey, cb), (has, cb) => has ? cb() : cb(new Error('No pins to load')), (cb) => repo.datastore.get(pinDataStoreKey, cb), - (mh, cb) => dag.get(new CID(mh), cb) + (mh, cb) => dag.get(new CID(mh), '', { preload: false }, cb) ], (err, pinRoot) => { if (err) { if (err.message === 'No pins to load') { diff --git a/src/core/runtime/preload-browser.js b/src/core/runtime/preload-browser.js index 8d123a12be..583b0a2128 100644 --- a/src/core/runtime/preload-browser.js +++ b/src/core/runtime/preload-browser.js @@ -11,7 +11,7 @@ module.exports = function preload (url, callback) { const req = new self.XMLHttpRequest() - req.open('HEAD', url) + req.open('GET', url) req.onreadystatechange = function () { if (this.readyState !== this.DONE) { diff --git a/test/cli/bootstrap.js b/test/cli/bootstrap.js index a301bca3f2..f71fee2d84 100644 --- a/test/cli/bootstrap.js +++ b/test/cli/bootstrap.js @@ -30,8 +30,6 @@ describe('bootstrap', () => runOnAndOff((thing) => { '/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', - '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] @@ -54,8 +52,6 @@ describe('bootstrap', () => runOnAndOff((thing) => { '/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', - '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/ip4/111.111.111.111/tcp/1001/ipfs/QmcyFFKfLDGJKwufn2GeitxvhricsBQyNKTkrD14psikoD' diff --git a/test/core/bootstrap.spec.js b/test/core/bootstrap.spec.js index d95d622f18..72cae70840 100644 --- a/test/core/bootstrap.spec.js +++ b/test/core/bootstrap.spec.js @@ -58,8 +58,6 @@ describe('bootstrap', () => { '/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', - '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6' ] @@ -82,8 +80,6 @@ describe('bootstrap', () => { '/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd', '/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3', '/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx', - '/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', - '/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic', '/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6', '/ip4/111.111.111.111/tcp/1001/ipfs/QmXFX2P5ammdmXQgfqGkfswtEVFsZUJ5KeHRXQYCTdiTAb' diff --git a/test/core/mfs-preload.spec.js b/test/core/mfs-preload.spec.js index 98d1fb70ed..d35bffd628 100644 --- a/test/core/mfs-preload.spec.js +++ b/test/core/mfs-preload.spec.js @@ -23,7 +23,9 @@ const createMockPreload = () => { } describe('MFS preload', () => { - it('should preload MFS root periodically', (done) => { + it('should preload MFS root periodically', function (done) { + this.timeout(80 * 1000) + // CIDs returned from our mock files.stat function const statCids = ['QmInitial', 'QmSame', 'QmSame', 'QmUpdated'] // The CIDs we expect to have been preloaded @@ -48,7 +50,7 @@ describe('MFS preload', () => { ).to.deep.equal(expectedPreloadCids) done() }) - }, statCids.length * (interval + 5)) + }, statCids.length * (interval * 2)) }) }) }) diff --git a/test/core/pin-set.js b/test/core/pin-set.js index 78f30b9c65..33b50ad144 100644 --- a/test/core/pin-set.js +++ b/test/core/pin-set.js @@ -53,7 +53,7 @@ describe('pinSet', function () { let repo before(function (done) { - this.timeout(20 * 1000) + this.timeout(80 * 1000) repo = createTempRepo() ipfs = new IPFS({ repo }) ipfs.on('ready', () => { @@ -63,7 +63,7 @@ describe('pinSet', function () { }) after(function (done) { - this.timeout(20 * 1000) + this.timeout(80 * 1000) ipfs.stop(done) }) diff --git a/test/core/preload.spec.js b/test/core/preload.spec.js index 38db0f3412..dc8527c552 100644 --- a/test/core/preload.spec.js +++ b/test/core/preload.spec.js @@ -97,6 +97,49 @@ describe('preload', () => { }) }) + it('should preload content retrieved with files.cat', (done) => { + ipfs.files.add(Buffer.from(hat()), { preload: false }, (err, res) => { + expect(err).to.not.exist() + ipfs.files.cat(res[0].hash, (err) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(res[0].hash, done) + }) + }) + }) + + it('should preload content retrieved with files.get', (done) => { + ipfs.files.add(Buffer.from(hat()), { preload: false }, (err, res) => { + expect(err).to.not.exist() + ipfs.files.get(res[0].hash, (err) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(res[0].hash, done) + }) + }) + }) + + it('should preload content retrieved with ls', (done) => { + ipfs.files.add([{ + path: 'dir0/dir1/file0', + content: Buffer.from(hat()) + }, { + path: 'dir0/dir1/file1', + content: Buffer.from(hat()) + }, { + path: 'dir0/file2', + content: Buffer.from(hat()) + }], { wrapWithDirectory: true }, (err, res) => { + expect(err).to.not.exist() + + const wrappingDir = res.find(file => file.path === '') + expect(wrappingDir).to.exist() + + ipfs.ls(wrappingDir.hash, (err) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(wrappingDir.hash, done) + }) + }) + }) + it('should preload content added with object.new', (done) => { ipfs.object.new((err, node) => { expect(err).to.not.exist() @@ -186,6 +229,17 @@ describe('preload', () => { }) }) + it('should preload content retrieved with object.get', (done) => { + ipfs.object.new(null, { preload: false }, (err, node) => { + expect(err).to.not.exist() + ipfs.object.get(node.multihash, (err) => { + expect(err).to.not.exist() + const cid = new CID(node.multihash) + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + }) + it('should preload content added with block.put', (done) => { ipfs.block.put(Buffer.from(hat()), (err, block) => { expect(err).to.not.exist() @@ -193,6 +247,26 @@ describe('preload', () => { }) }) + it('should preload content retrieved with block.get', (done) => { + ipfs.block.put(Buffer.from(hat()), { preload: false }, (err, block) => { + expect(err).to.not.exist() + ipfs.block.get(block.cid, (err) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(block.cid.toBaseEncodedString(), done) + }) + }) + }) + + it('should preload content retrieved with block.stat', (done) => { + ipfs.block.put(Buffer.from(hat()), { preload: false }, (err, block) => { + expect(err).to.not.exist() + ipfs.block.stat(block.cid, (err) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(block.cid.toBaseEncodedString(), done) + }) + }) + }) + it('should preload content added with dag.put', (done) => { const obj = { test: hat() } ipfs.dag.put(obj, { format: 'dag-cbor', hashAlg: 'sha2-256' }, (err, cid) => { @@ -200,4 +274,16 @@ describe('preload', () => { MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) }) }) + + it('should preload content retrieved with dag.get', (done) => { + const obj = { test: hat() } + const opts = { format: 'dag-cbor', hashAlg: 'sha2-256', preload: false } + ipfs.dag.put(obj, opts, (err, cid) => { + expect(err).to.not.exist() + ipfs.dag.get(cid, (err) => { + expect(err).to.not.exist() + MockPreloadNode.waitForCids(cid.toBaseEncodedString(), done) + }) + }) + }) }) diff --git a/test/fixtures/go-ipfs-repo/config b/test/fixtures/go-ipfs-repo/config index 9843d866a8..162598cdfc 100644 --- a/test/fixtures/go-ipfs-repo/config +++ b/test/fixtures/go-ipfs-repo/config @@ -64,8 +64,6 @@ "/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd", "/ip6/2a03:b0c0:1:d0::e7:1/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3", "/ip6/2604:a880:1:20::1d9:6001/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx", - "/dns4/wss0.bootstrap.libp2p.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic", - "/dns4/wss1.bootstrap.libp2p.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6", "/dns4/node0.preload.ipfs.io/tcp/443/wss/ipfs/QmZMxNdpMkewiVZLMRxaNxUeZpDUb34pWjZ1kZvsd16Zic", "/dns4/node1.preload.ipfs.io/tcp/443/wss/ipfs/Qmbut9Ywz9YEDrz8ySBSgWyJk41Uvm2QJPhwDJzJyGFsD6" ], From 21b3015a30d1f2f91e788fcd516a22da2c2164d6 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 10:17:04 +0100 Subject: [PATCH 07/51] chore: update contributors --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 36460d3a92..e41fcb9f6e 100644 --- a/package.json +++ b/package.json @@ -260,6 +260,7 @@ "kumavis ", "nginnever ", "npmcdn-to-unpkg-bot ", + "robbsolter <35879806+robbsolter@users.noreply.github.com>", "seungwon-kang ", "tcme ", "Łukasz Magiera ", From 581816a0f238c6cae2eed8c295900b5fc04d6f03 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 10:17:05 +0100 Subject: [PATCH 08/51] chore: release version v0.31.0 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8292a2da8d..0716e030aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ + +# [0.31.0](https://github.com/ipfs/js-ipfs/compare/v0.30.1...v0.31.0) (2018-07-29) + + +### Bug Fixes + +* emit boot error only once ([#1472](https://github.com/ipfs/js-ipfs/issues/1472)) ([45b80a0](https://github.com/ipfs/js-ipfs/commit/45b80a0)) + + +### Features + +* preload content ([#1464](https://github.com/ipfs/js-ipfs/issues/1464)) ([bffe080](https://github.com/ipfs/js-ipfs/commit/bffe080)), closes [#1459](https://github.com/ipfs/js-ipfs/issues/1459) +* preload on content fetch requests ([#1475](https://github.com/ipfs/js-ipfs/issues/1475)) ([649b755](https://github.com/ipfs/js-ipfs/commit/649b755)), closes [#1473](https://github.com/ipfs/js-ipfs/issues/1473) +* remove decomissioned bootstrappers ([e3868f4](https://github.com/ipfs/js-ipfs/commit/e3868f4)) +* rm decomissioned bootstrappers - nodejs ([90e9f68](https://github.com/ipfs/js-ipfs/commit/90e9f68)) +* support --raw-leaves ([#1454](https://github.com/ipfs/js-ipfs/issues/1454)) ([1f63e8c](https://github.com/ipfs/js-ipfs/commit/1f63e8c)) + + +### Reverts + +* docs: add migration note about upgrading from < 0.30.0 ([#1450](https://github.com/ipfs/js-ipfs/issues/1450)) ([#1456](https://github.com/ipfs/js-ipfs/issues/1456)) ([f4344b0](https://github.com/ipfs/js-ipfs/commit/f4344b0)) + + + ## [0.30.1](https://github.com/ipfs/js-ipfs/compare/v0.30.0...v0.30.1) (2018-07-17) diff --git a/package.json b/package.json index e41fcb9f6e..8725330103 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.30.1", + "version": "0.31.0", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From a9219add561ca6efb7bec68bacc6380011740a06 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 10:36:30 +0100 Subject: [PATCH 09/51] fix: logo link License: MIT Signed-off-by: Alan Shaw --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2dbefa5b3..212f45f86c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- + IPFS in JavaScript logo

From 7d6f0ca9e95fdf2392a16343a9caae5472595954 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 23:39:01 +0100 Subject: [PATCH 10/51] fix: XMLHTTPRequest is deprecated and unavailable in service workers (#1478) This PR switches to using the fetch API instead. License: MIT Signed-off-by: Alan Shaw --- src/core/runtime/preload-browser.js | 36 +++++++++++------------------ 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/core/runtime/preload-browser.js b/src/core/runtime/preload-browser.js index 583b0a2128..ff5b9b7698 100644 --- a/src/core/runtime/preload-browser.js +++ b/src/core/runtime/preload-browser.js @@ -9,29 +9,21 @@ log.error = debug('jsipfs:preload:error') module.exports = function preload (url, callback) { log(url) - const req = new self.XMLHttpRequest() - - req.open('GET', url) - - req.onreadystatechange = function () { - if (this.readyState !== this.DONE) { - return - } - - if (this.status < 200 || this.status >= 300) { - log.error('failed to preload', url, this.status, this.statusText) - return callback(new Error(`failed to preload ${url}`)) - } - - callback() - } - - req.send() + const controller = new AbortController() + const signal = controller.signal + + fetch(url, { signal }) + .then(res => { + if (!res.ok) { + log.error('failed to preload', url, res.status, res.statusText) + throw new Error(`failed to preload ${url}`) + } + return res.text() + }) + .then(() => callback()) + .catch(callback) return { - cancel: () => { - req.abort() - callback(new Error('request aborted')) - } + cancel: () => controller.abort() } } From 3bc350318d80843a64ea4cd2208d537a9944bd96 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 23:50:33 +0100 Subject: [PATCH 11/51] chore: update contributors From 5e80ee3a5ce7b509b164fdcc6816f0cfca896047 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Sun, 29 Jul 2018 23:50:34 +0100 Subject: [PATCH 12/51] chore: release version v0.31.1 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0716e030aa..c279f5dcc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +## [0.31.1](https://github.com/ipfs/js-ipfs/compare/v0.31.0...v0.31.1) (2018-07-29) + + +### Bug Fixes + +* logo link ([a9219ad](https://github.com/ipfs/js-ipfs/commit/a9219ad)) +* XMLHTTPRequest is deprecated and unavailable in service workers ([#1478](https://github.com/ipfs/js-ipfs/issues/1478)) ([7d6f0ca](https://github.com/ipfs/js-ipfs/commit/7d6f0ca)) + + + # [0.31.0](https://github.com/ipfs/js-ipfs/compare/v0.30.1...v0.31.0) (2018-07-29) diff --git a/package.json b/package.json index 8725330103..fdfaeabac9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.0", + "version": "0.31.1", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From d528b3f468ffea735069cc279e05b5c3c6d14d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Thu, 2 Aug 2018 07:04:54 -0700 Subject: [PATCH 13/51] fix: fix content-type by doing a fall-back using extensions (#1482) The JS IPFS gateway was only responding with the correct content-type for some mime-types, see https://github.com/sindresorhus/file-type#supported-file-types. This commit now fall-backs to detecting based on the extension as well. Note that SVGs aren't supported by the `file-type` module. --- src/http/gateway/resources/gateway.js | 46 ++++++++------- test/gateway/index.js | 50 ++++++++++++++++- .../unsniffable-folder/hexagons-xml.svg | 56 +++++++++++++++++++ .../unsniffable-folder/hexagons.svg | 55 ++++++++++++++++++ 4 files changed, 185 insertions(+), 22 deletions(-) create mode 100644 test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg create mode 100644 test/gateway/test-folder/unsniffable-folder/hexagons.svg diff --git a/src/http/gateway/resources/gateway.js b/src/http/gateway/resources/gateway.js index c3a866c359..856bf4a8d9 100644 --- a/src/http/gateway/resources/gateway.js +++ b/src/http/gateway/resources/gateway.js @@ -12,6 +12,21 @@ const Stream = require('readable-stream') const { resolver } = require('ipfs-http-response') const PathUtils = require('../utils/path') +function detectContentType (ref, chunk) { + let fileSignature + + // try to guess the filetype based on the first bytes + // note that `file-type` doesn't support svgs, therefore we assume it's a svg if ref looks like it + if (!ref.endsWith('.svg')) { + fileSignature = fileType(chunk) + } + + // if we were unable to, fallback to the `ref` which might contain the extension + const mimeType = mime.lookup(fileSignature ? fileSignature.ext : ref) + + return mime.contentType(mimeType) +} + module.exports = { checkCID: (request, reply) => { if (!request.params.cid) { @@ -97,7 +112,7 @@ module.exports = { } // response.continue() - let filetypeChecked = false + let contentTypeDetected = false let stream2 = new Stream.PassThrough({ highWaterMark: 1 }) stream2.on('error', (err) => { log.error('stream2 err: ', err) @@ -108,29 +123,20 @@ module.exports = { pull( toPull.source(stream), pull.through((chunk) => { - // Check file type. do this once. - if (chunk.length > 0 && !filetypeChecked) { - log('got first chunk') - let fileSignature = fileType(chunk) - log('file type: ', fileSignature) - - filetypeChecked = true - const mimeType = mime.lookup(fileSignature - ? fileSignature.ext - : null) + // Guess content-type (only once) + if (chunk.length > 0 && !contentTypeDetected) { + let contentType = detectContentType(ref, chunk) + contentTypeDetected = true log('ref ', ref) - log('mime-type ', mimeType) - - if (mimeType) { - log('writing mimeType') + log('mime-type ', contentType) - response - .header('Content-Type', mime.contentType(mimeType)) - .send() - } else { - response.send() + if (contentType) { + log('writing content-type header') + response.header('Content-Type', contentType) } + + response.send() } stream2.write(chunk) diff --git a/test/gateway/index.js b/test/gateway/index.js index 84ce39914a..1c319671e5 100644 --- a/test/gateway/index.js +++ b/test/gateway/index.js @@ -19,7 +19,9 @@ const directoryContent = { 'nested-folder/hello.txt': loadFixture('test/gateway/test-folder/nested-folder/hello.txt'), 'nested-folder/ipfs.txt': loadFixture('test/gateway/test-folder/nested-folder/ipfs.txt'), 'nested-folder/nested.html': loadFixture('test/gateway/test-folder/nested-folder/nested.html'), - 'cat-folder/cat.jpg': loadFixture('test/gateway/test-folder/cat-folder/cat.jpg') + 'cat-folder/cat.jpg': loadFixture('test/gateway/test-folder/cat-folder/cat.jpg'), + 'unsniffable-folder/hexagons-xml.svg': loadFixture('test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg'), + 'unsniffable-folder/hexagons.svg': loadFixture('test/gateway/test-folder/unsniffable-folder/hexagons.svg') } describe('HTTP Gateway', function () { @@ -113,6 +115,22 @@ describe('HTTP Gateway', function () { expect(file.hash).to.equal(expectedMultihash) cb() }) + }, + (cb) => { + const expectedMultihash = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F' + + let dir = [ + content('unsniffable-folder/hexagons-xml.svg'), + content('unsniffable-folder/hexagons.svg') + ] + + http.api.node.files.add(dir, (err, res) => { + expect(err).to.not.exist() + const file = res[res.length - 2] + expect(file.path).to.equal('test-folder/unsniffable-folder') + expect(file.hash).to.equal(expectedMultihash) + cb() + }) } ], done) }) @@ -166,7 +184,7 @@ describe('HTTP Gateway', function () { }) }) - it('load a non text file', (done) => { + it('load a jpg file', (done) => { let kitty = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg' gateway.inject({ @@ -184,6 +202,34 @@ describe('HTTP Gateway', function () { }) }) + it('load a svg file (unsniffable)', (done) => { + let hexagons = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F/hexagons.svg' + + gateway.inject({ + method: 'GET', + url: '/ipfs/' + hexagons + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.headers['content-type']).to.equal('image/svg+xml') + + done() + }) + }) + + it('load a svg file with xml leading declaration (unsniffable)', (done) => { + let hexagons = 'QmVZoGxDvKM9KExc8gaL4uTbhdNtWhzQR7ndrY7J1gWs3F/hexagons-xml.svg' + + gateway.inject({ + method: 'GET', + url: '/ipfs/' + hexagons + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.headers['content-type']).to.equal('image/svg+xml') + + done() + }) + }) + it('load a directory', (done) => { let dir = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/' diff --git a/test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg b/test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg new file mode 100644 index 0000000000..fe6b79dfd2 --- /dev/null +++ b/test/gateway/test-folder/unsniffable-folder/hexagons-xml.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/gateway/test-folder/unsniffable-folder/hexagons.svg b/test/gateway/test-folder/unsniffable-folder/hexagons.svg new file mode 100644 index 0000000000..557d927b39 --- /dev/null +++ b/test/gateway/test-folder/unsniffable-folder/hexagons.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5d4b51e8d33c5d4c5482050579b10270e72f7a49 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 2 Aug 2018 07:46:30 -0700 Subject: [PATCH 14/51] chore: update contributors --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fdfaeabac9..38c5f2b41b 100644 --- a/package.json +++ b/package.json @@ -180,6 +180,7 @@ "Alan Shaw ", "Alex Potsides ", "Andrew de Andrade ", + "André Cruz ", "Arpit Agarwal <93arpit@gmail.com>", "Arpit Agarwal ", "Bernard Mordan ", From 7860d0f1ce4b3d74654aa5b8e7d4c66dcac2a34b Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 2 Aug 2018 07:46:31 -0700 Subject: [PATCH 15/51] chore: release version v0.31.2 --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c279f5dcc7..831c6ef999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [0.31.2](https://github.com/ipfs/js-ipfs/compare/v0.31.1...v0.31.2) (2018-08-02) + + +### Bug Fixes + +* fix content-type by doing a fall-back using extensions ([#1482](https://github.com/ipfs/js-ipfs/issues/1482)) ([d528b3f](https://github.com/ipfs/js-ipfs/commit/d528b3f)) + + + ## [0.31.1](https://github.com/ipfs/js-ipfs/compare/v0.31.0...v0.31.1) (2018-07-29) diff --git a/package.json b/package.json index 38c5f2b41b..be757cac40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.1", + "version": "0.31.2", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From 99911b19179815b5ee2ad4f11f3bd25c257012dd Mon Sep 17 00:00:00 2001 From: Pascal Precht Date: Thu, 2 Aug 2018 19:00:08 +0200 Subject: [PATCH 16/51] fix(dht): allow for options object in `findProvs()` API (#1457) This is to complement: https://github.com/ipfs/interface-ipfs-core/pull/337 Fixes #1322 --- src/core/components/dht.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/components/dht.js b/src/core/components/dht.js index 8504df7488..e5edf281db 100644 --- a/src/core/components/dht.js +++ b/src/core/components/dht.js @@ -56,12 +56,19 @@ module.exports = (self) => { * @param {function(Error, Array)} [callback] * @returns {Promise|void} */ - findprovs: promisify((key, callback) => { + findprovs: promisify((key, opts, callback) => { if (typeof key === 'string') { key = new CID(key) } - self._libp2pNode.contentRouting.findProviders(key, callback) + if (typeof opts === 'function') { + callback = opts + opts = {} + } + + opts = opts || {} + + self._libp2pNode.contentRouting.findProviders(key, opts.timeout || null, callback) }), /** From e60756098affbaa9a9ec20e8142fe37c337ac7ac Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 7 Aug 2018 09:45:54 +0100 Subject: [PATCH 17/51] fix: failing tests in master (#1488) License: MIT Signed-off-by: Alan Shaw --- package.json | 4 ++-- test/core/interface.spec.js | 32 ++++++++++++++++++++++++++++++-- test/http-api/interface.js | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index be757cac40..00ce13e516 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "expose-loader": "~0.7.5", "form-data": "^2.3.2", "hat": "0.0.3", - "interface-ipfs-core": "~0.72.1", - "ipfsd-ctl": "~0.38.0", + "interface-ipfs-core": "~0.75.1", + "ipfsd-ctl": "~0.39.0", "mocha": "^5.2.0", "ncp": "^2.0.0", "nexpect": "~0.5.0", diff --git a/test/core/interface.spec.js b/test/core/interface.spec.js index 73742776a7..07fb19a6b5 100644 --- a/test/core/interface.spec.js +++ b/test/core/interface.spec.js @@ -22,7 +22,28 @@ describe('interface-ipfs-core tests', () => { skip: { reason: 'TODO: DHT is not implemented in js-ipfs yet!' } }) - tests.files(defaultCommonFactory) + tests.files(defaultCommonFactory, { + skip: [ + // files.ls + { + name: 'should ls directory', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' + }, + { + name: 'should ls -l directory', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' + }, + // files.read*Stream + { + name: 'readPullStream', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' + }, + { + name: 'readReadableStream', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' + } + ] + }) tests.key(CommonFactory.create({ spawnOptions: { @@ -36,7 +57,14 @@ describe('interface-ipfs-core tests', () => { tests.miscellaneous(CommonFactory.create({ // No need to stop, because the test suite does a 'stop' test. createTeardown: () => cb => cb() - })) + }), { + skip: [ + { + name: 'resolve', + reason: 'TODO: not implemented' + } + ] + }) tests.object(defaultCommonFactory) diff --git a/test/http-api/interface.js b/test/http-api/interface.js index e123093687..9a1cc20e00 100644 --- a/test/http-api/interface.js +++ b/test/http-api/interface.js @@ -25,7 +25,28 @@ describe('interface-ipfs-core over ipfs-api tests', () => { skip: { reason: 'TODO: DHT is not implemented in js-ipfs yet!' } }) - tests.files(defaultCommonFactory) + tests.files(defaultCommonFactory, { + skip: [ + // files.ls + { + name: 'should ls directory', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' + }, + { + name: 'should ls -l directory', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' + }, + // files.read*Stream + { + name: 'readPullStream', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' + }, + { + name: 'readReadableStream', + reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' + } + ] + }) tests.key(CommonFactory.create({ spawnOptions: { @@ -37,7 +58,14 @@ describe('interface-ipfs-core over ipfs-api tests', () => { tests.miscellaneous(CommonFactory.create({ // No need to stop, because the test suite does a 'stop' test. createTeardown: () => cb => cb() - })) + }), { + skip: [ + { + name: 'resolve', + reason: 'TODO: not implemented' + } + ] + }) tests.object(defaultCommonFactory) From 29b5b33228ce271650c71ba2228138230bead130 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Tue, 7 Aug 2018 11:46:56 +0100 Subject: [PATCH 18/51] test: enable bitswap tests on windows (#1483) License: MIT Signed-off-by: Alan Shaw --- package.json | 4 ++-- test/core/bitswap.spec.js | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 00ce13e516..cca64e6917 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "form-data": "^2.3.2", "hat": "0.0.3", "interface-ipfs-core": "~0.75.1", - "ipfsd-ctl": "~0.39.0", + "ipfsd-ctl": "~0.39.1", "mocha": "^5.2.0", "ncp": "^2.0.0", "nexpect": "~0.5.0", @@ -109,7 +109,7 @@ "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", "ipfs-http-response": "~0.1.2", - "ipfs-mfs": "~0.2.3", + "ipfs-mfs": "~0.2.5", "ipfs-multipart": "~0.1.0", "ipfs-repo": "~0.22.1", "ipfs-unixfs": "~0.1.15", diff --git a/test/core/bitswap.spec.js b/test/core/bitswap.spec.js index 645581f2c1..ebadb2b99b 100644 --- a/test/core/bitswap.spec.js +++ b/test/core/bitswap.spec.js @@ -20,11 +20,6 @@ const IPFSFactory = require('ipfsd-ctl') const IPFS = require('../../src/core') -// TODO bitswap tests on windows is failing, missing proper shutdown of daemon -// https://github.com/ipfs/js-ipfsd-ctl/pull/205 -const isWindows = require('../utils/platforms').isWindows -const skipOnWindows = isWindows() ? describe.skip : describe - function makeBlock (callback) { const d = Buffer.from(`IPFS is awesome ${Math.random()}`) @@ -89,7 +84,7 @@ function addNode (fDaemon, inProcNode, callback) { }) } -skipOnWindows('bitswap', function () { +describe('bitswap', function () { this.timeout(80 * 1000) let inProcNode // Node spawned inside this process From ff59a2f1f5d6df6083d47bdcb43fa481121dded7 Mon Sep 17 00:00:00 2001 From: David Gilbertson Date: Tue, 7 Aug 2018 20:48:21 +1000 Subject: [PATCH 19/51] Update JavaScript syntax highlighting (#1490) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 212f45f86c..4d8b63a2b6 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Requires npm@3 and node@6 or above, tested on OSX & Linux, expected to work on W To include this project programmatically: -```JavaScript +```js const IPFS = require('ipfs') const node = new IPFS() ``` @@ -174,7 +174,7 @@ If you want a programmatic way to spawn a IPFS Daemon using JavaScript, check ou Use the IPFS Module as a dependency of a project to __spawn in process instances of IPFS__. Create an instance by calling `new IPFS()` and waiting for its `ready` event: -```JavaScript +```js // Create the IPFS node instance const node = new IPFS() From d0b671b6533fda14f380e8f81c5a3a2691e6bc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=BB=D1=8C?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Wed, 8 Aug 2018 14:41:40 +0300 Subject: [PATCH 20/51] fix(dag): check dag.put options for plain object (#1480) * fix(dag): check dag.put options for plain object fixes #1479 * fix(dag): check for undefined options value * fix: increase timeout for pin after hook License: MIT Signed-off-by: Alan Shaw --- src/core/components/dag.js | 6 ++++-- test/core/pin.js | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/components/dag.js b/src/core/components/dag.js index e8cecdb7a4..c33539d97c 100644 --- a/src/core/components/dag.js +++ b/src/core/components/dag.js @@ -11,11 +11,13 @@ module.exports = function dag (self) { put: promisify((dagNode, options, callback) => { if (typeof options === 'function') { callback = options - } else if (options.cid && (options.format || options.hashAlg)) { + options = {} + } else if (options && options.cid && (options.format || options.hashAlg)) { return callback(new Error('Can\'t put dag node. Please provide either `cid` OR `format` and `hashAlg` options.')) - } else if ((options.format && !options.hashAlg) || (!options.format && options.hashAlg)) { + } else if (options && ((options.format && !options.hashAlg) || (!options.format && options.hashAlg))) { return callback(new Error('Can\'t put dag node. Please provide `format` AND `hashAlg` options.')) } + options = options || {} const optionDefaults = { format: 'dag-cbor', diff --git a/test/core/pin.js b/test/core/pin.js index 32618422a0..2c1b87e329 100644 --- a/test/core/pin.js +++ b/test/core/pin.js @@ -81,7 +81,10 @@ describe('pin', function () { }) }) - after(done => ipfs.stop(done)) + after(function (done) { + this.timeout(20 * 1000) + ipfs.stop(done) + }) describe('isPinnedWithType', function () { beforeEach(function () { From 03d31cdf06c1491582539955efe1afbc1852e7de Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 09:14:04 +0100 Subject: [PATCH 21/51] docs: make constructor options linkable (#1486) * docs: make constructor options linkable This PR changes the constructor options into headings so that people can link the relevant options. I have needed this multiple times but haven't been able to give people a link to the correct part of the page. License: MIT Signed-off-by: Alan Shaw * Fix README typos and add links where we now can This also unifies the style in all the places where default values are different in various environments: use a comma between each environment. License: MIT Signed-off-by: Rob Brackett --- README.md | 144 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 4d8b63a2b6..36c16e7ada 100644 --- a/README.md +++ b/README.md @@ -202,56 +202,122 @@ const node = new IPFS([options]) Creates and returns an instance of an IPFS node. Use the `options` argument to specify advanced configuration. It is an object with any of these properties: -- `repo` (string or [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance): The file path at which to store the IPFS node’s data. Alternatively, you can set up a customized storage system by providing an [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance. (Default: `'~/.jsipfs'` in Node.js, `'ipfs'` in browsers.) - Example: +##### `options.repo` - ```js - // Store data outside your user directory - const node = new IPFS({ repo: '/var/ipfs/data' }) - ``` +| Type | Default | +|------|---------| +| string or [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance | `'~/.jsipfs'` in Node.js, `'ipfs'` in browsers | + +The file path at which to store the IPFS node’s data. Alternatively, you can set up a customized storage system by providing an [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo) instance. + +Example: + +```js +// Store data outside your user directory +const node = new IPFS({ repo: '/var/ipfs/data' }) +``` + +##### `options.init` + +| Type | Default | +|------|---------| +| boolean or object | `true` | + +Initialize the repo when creating the IPFS node. + +If you have already initialized a repo before creating your IPFS node (e.g. you are loading a repo that was saved to disk from a previous run of your program), you must make sure to set this to `false`. Note that *initializing* a repo is different from creating an instance of [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo). The IPFS constructor sets many special properties when initializing a repo, so you should usually not try and call `repoInstance.init()` yourself. + +Instead of a boolean, you may provide an object with custom initialization options. All properties are optional: + +- `emptyRepo` (boolean) Whether to remove built-in assets, like the instructional tour and empty mutable file system, from the repo. (Default: `false`) +- `bits` (number) Number of bits to use in the generated key pair. (Default: `2048`) +- `pass` (string) A passphrase to encrypt keys. You should generally use the [top-level `pass` option](#optionspass) instead of the `init.pass` option (this one will take its value from the top-level option if not set). + +##### `options.start` + +| Type | Default | +|------|---------| +| boolean | `true` | + + If `false`, do not automatically start the IPFS node. Instead, you’ll need to manually call [`node.start()`](#nodestartcallback) yourself. + +##### `options.pass` + +| Type | Default | +|------|---------| +| string | `null` | + +A passphrase to encrypt/decrypt your keys. + +##### `options.relay` + +| Type | Default | +|------|---------| +| object | `{ enabled: false, hop: { enabled: false, active: false } }` | + +Configure circuit relay (see the [circuit relay tutorial](https://github.com/ipfs/js-ipfs/tree/master/examples/circuit-relaying) to learn more). + +- `enabled` (boolean): Enable circuit relay dialer and listener. (Default: `false`) +- `hop` (object) + - `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`) + - `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`) + +##### `options.preload` + +| Type | Default | +|------|---------| +| object | `{ enabled: true, addresses: [...] }` | + +Configure external nodes that will preload content added to this node. + +- `enabled` (boolean): Enable content preloading (Default: `true`) +- `addresses` (array): Multiaddr API addresses of nodes that should preload content. **NOTE:** nodes specified here should also be added to your node's bootstrap address list at [`config.Boostrap`](#optionsconfig). + +##### `options.EXPERIMENTAL` + +| Type | Default | +|------|---------| +| object | `{ pubsub: false, sharding: false, dht: false }` | + +Enable and configure experimental features. -- `init` (boolean or object): Initialize the repo when creating the IPFS node. (Default: `true`) +- `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`) +- `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`) +- `dht` (boolean): Enable KadDHT. **This is currently not interopable with `go-ipfs`.** - If you have already initialized a repo before creating your IPFS node (e.g. you are loading a repo that was saved to disk from a previous run of your program), you must make sure to set this to `false`. Note that *initializing* a repo is different from creating an instance of [`ipfs.Repo`](https://github.com/ipfs/js-ipfs-repo). The IPFS constructor sets many special properties when initializing a repo, so you should usually not try and call `repoInstance.init()` yourself. +##### `options.config` - Instead of a boolean, you may provide an object with custom initialization options. All properties are optional: +| Type | Default | +|------|---------| +| object | [`config-nodejs.js`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-nodejs.js) in Node.js, [`config-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-browser.js) in browsers | - - `init.emptyRepo` (boolean) Whether to remove built-in assets, like the instructional tour and empty mutable file system, from the repo. (Default: `false`) - - `init.bits` (number) Number of bits to use in the generated key pair. (Default: `2048`) - - `init.pass` (string) A passphrase to encrypt keys. You should generally use the top-level `pass` option instead of the `init.pass` option (this one will take its value from the top-level option if not set). +Modify the default IPFS node config. This object will be *merged* with the default config; it will not replace it. -- `start` (boolean): If `false`, do not automatically start the IPFS node. Instead, you’ll need to manually call `node.start()` yourself. (Default: `true`) +##### `options.libp2p` -- `pass` (string): A passphrase to encrypt/decrypt your keys. +| Type | Default | +|------|---------| +| object | [`libp2p-nodejs.js`](https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/libp2p-nodejs.js) in Node.js, [`libp2p-browser.js`](https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/libp2p-browser.js) in browsers | -- `relay` (object): Configure circuit relay (see the [circuit relay tutorial](https://github.com/ipfs/js-ipfs/tree/master/examples/circuit-relaying) to learn more). - - `enabled` (boolean): Enable circuit relay dialer and listener. (Default: `false`) - - `hop` (object) - - `enabled` (boolean): Make this node a relay (other nodes can connect *through* it). (Default: `false`) - - `active` (boolean): Make this an *active* relay node. Active relay nodes will attempt to dial a destination peer even if that peer is not yet connected to the relay. (Default: `false`) +Add custom modules to the libp2p stack of your node. -- `preload` (object): Configure external nodes that will preload content added to this node - - `enabled` (boolean): Enable content preloading (Default: `true`) - - `addresses` (array): Multiaddr API addresses of nodes that should preload content. NOTE: nodes specified here should also be added to your node's bootstrap address list at `config.Boostrap` -- `EXPERIMENTAL` (object): Enable and configure experimental features. - - `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`) - - `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`) - - `dht` (boolean): Enable KadDHT. **This is currently not interopable with `go-ipfs`.** +- `modules` (object): + - `transport` (Array<[libp2p.Transport](https://github.com/libp2p/interface-transport)>): An array of Libp2p transport classes/instances to use _instead_ of the defaults. See [libp2p/interface-transport](https://github.com/libp2p/interface-transport) for details. + - `peerDiscovery` (Array<[libp2p.PeerDiscovery](https://github.com/libp2p/interface-peer-discovery)>): An array of Libp2p peer discovery classes/instances to use _instead_ of the defaults. See [libp2p/peer-discovery](https://github.com/libp2p/interface-peer-discovery) for details. If passing a class, configuration can be passed using the config section below under the key corresponding to you module's unique `tag` (a static property on the class) +- `config` (object): + - `peerDiscovery` (object): + - `[PeerDiscovery.tag]` (object): configuration for a peer discovery module + - `enabled` (boolean): whether this module is enabled or disabled + - `[custom config]` (any): other keys are specific to the module -- `config` (object) Modify the default IPFS node config. Find the Node.js defaults at [`src/core/runtime/config-nodejs.js`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-nodejs.js) and the browser defaults at [`src/core/runtime/config-browser.js`](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/config-browser.js). This object will be *merged* with the default config; it will not replace it. +##### `options.connectionManager` -- `libp2p` (object) add custom modules to the libp2p stack of your node - - `modules` (object): - - `transport` (Array<[libp2p.Transport](https://github.com/libp2p/interface-transport)>): An array of Libp2p transport classes/instances to use _instead_ of the defaults. See [libp2p/interface-transport](https://github.com/libp2p/interface-transport) for details. - - `peerDiscovery` (Array<[libp2p.PeerDiscovery](https://github.com/libp2p/interface-peer-discovery)>): An array of Libp2p peer discovery classes/instances to use _instead_ of the defaults. See [libp2p/peer-discovery](https://github.com/libp2p/interface-peer-discovery) for details. If passing a class, configuration can be passed using the config section below under the key corresponding to you module's unique `tag` (a static property on the class) - - `config` (object): - - `peerDiscovery` (object): - - `[PeerDiscovery.tag]` (object): configuration for a peer discovery module - - `enabled` (boolean): whether this module is enabled or disabled - - `[custom config]` (any): other keys are specific to the module +| Type | Default | +|------|---------| +| object | [defaults](https://github.com/libp2p/js-libp2p-connection-manager#create-a-connectionmanager) | -- `connectionManager` (object): Configure the libp2p connection manager, see the [documentation for available options](https://github.com/libp2p/js-libp2p-connection-manager#create-a-connectionmanager). +Configure the libp2p connection manager. #### Events @@ -363,10 +429,10 @@ The core API is grouped into several areas: - [`ipfs.files.addPullStream([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesaddpullstream) - [`ipfs.files.addReadableStream([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesaddreadablestream) - [`ipfs.files.cat(ipfsPath, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filescat). Alias to `ipfs.cat`. - - [`ipfs.files.catPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filescatpullstream) + - [`ipfs.files.catPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filescatpullstream) - [`ipfs.files.catReadableStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filescatreadablestream) - [`ipfs.files.get(ipfsPath, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesget). Alias to `ipfs.get`. - - [`ipfs.files.getPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesgetpullstream) + - [`ipfs.files.getPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesgetpullstream) - [`ipfs.files.getReadableStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesgetreadablestream) - [`ipfs.ls(ipfsPath, [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#ls) - [`ipfs.lsPullStream(ipfsPath)`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#lspullstream) From 8456bc5482fc0e7bda18b2ae12ce91f3448b51a0 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 10:30:36 +0100 Subject: [PATCH 22/51] chore: update contributors --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index cca64e6917..7b16b247fb 100644 --- a/package.json +++ b/package.json @@ -193,6 +193,7 @@ "Daniel J. O'Quinn ", "Daniela Borges Matos de Carvalho ", "David Dias ", + "David Gilbertson ", "David da Silva ", "Diogo Silva ", "Dmitriy Ryajov ", @@ -265,6 +266,7 @@ "seungwon-kang ", "tcme ", "Łukasz Magiera ", + "Максим Ильин ", "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ " ] } From 86222643b3825df0a94e645e887d9b47763b5031 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 10:30:37 +0100 Subject: [PATCH 23/51] chore: release version v0.31.3 --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 831c6ef999..15687f2848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + +## [0.31.3](https://github.com/ipfs/js-ipfs/compare/v0.31.2...v0.31.3) (2018-08-09) + + +### Bug Fixes + +* failing tests in master ([#1488](https://github.com/ipfs/js-ipfs/issues/1488)) ([e607560](https://github.com/ipfs/js-ipfs/commit/e607560)) +* **dag:** check dag.put options for plain object ([#1480](https://github.com/ipfs/js-ipfs/issues/1480)) ([d0b671b](https://github.com/ipfs/js-ipfs/commit/d0b671b)), closes [#1479](https://github.com/ipfs/js-ipfs/issues/1479) +* **dht:** allow for options object in `findProvs()` API ([#1457](https://github.com/ipfs/js-ipfs/issues/1457)) ([99911b1](https://github.com/ipfs/js-ipfs/commit/99911b1)), closes [#1322](https://github.com/ipfs/js-ipfs/issues/1322) + + + ## [0.31.2](https://github.com/ipfs/js-ipfs/compare/v0.31.1...v0.31.2) (2018-08-02) diff --git a/package.json b/package.json index 7b16b247fb..445ce21e86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.2", + "version": "0.31.3", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From a0bc79b9b3d6b6ade95fe28a7d2f3dcb3466fd3a Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 9 Aug 2018 11:43:03 +0100 Subject: [PATCH 24/51] fix: files.ls and files.read*Stream tests (#1493) --- package.json | 6 +++--- test/core/interface.spec.js | 23 +---------------------- test/http-api/interface.js | 23 +---------------------- 3 files changed, 5 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 445ce21e86..a603cc1b9e 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "expose-loader": "~0.7.5", "form-data": "^2.3.2", "hat": "0.0.3", - "interface-ipfs-core": "~0.75.1", + "interface-ipfs-core": "~0.75.2", "ipfsd-ctl": "~0.39.1", "mocha": "^5.2.0", "ncp": "^2.0.0", @@ -109,11 +109,11 @@ "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", "ipfs-http-response": "~0.1.2", - "ipfs-mfs": "~0.2.5", + "ipfs-mfs": "~0.3.0", "ipfs-multipart": "~0.1.0", "ipfs-repo": "~0.22.1", "ipfs-unixfs": "~0.1.15", - "ipfs-unixfs-engine": "~0.31.3", + "ipfs-unixfs-engine": "~0.32.1", "ipld": "~0.17.3", "ipld-dag-cbor": "~0.12.1", "ipld-dag-pb": "~0.14.6", diff --git a/test/core/interface.spec.js b/test/core/interface.spec.js index 07fb19a6b5..67b9f974b3 100644 --- a/test/core/interface.spec.js +++ b/test/core/interface.spec.js @@ -22,28 +22,7 @@ describe('interface-ipfs-core tests', () => { skip: { reason: 'TODO: DHT is not implemented in js-ipfs yet!' } }) - tests.files(defaultCommonFactory, { - skip: [ - // files.ls - { - name: 'should ls directory', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' - }, - { - name: 'should ls -l directory', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' - }, - // files.read*Stream - { - name: 'readPullStream', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' - }, - { - name: 'readReadableStream', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' - } - ] - }) + tests.files(defaultCommonFactory) tests.key(CommonFactory.create({ spawnOptions: { diff --git a/test/http-api/interface.js b/test/http-api/interface.js index 9a1cc20e00..b27c2b22fb 100644 --- a/test/http-api/interface.js +++ b/test/http-api/interface.js @@ -25,28 +25,7 @@ describe('interface-ipfs-core over ipfs-api tests', () => { skip: { reason: 'TODO: DHT is not implemented in js-ipfs yet!' } }) - tests.files(defaultCommonFactory, { - skip: [ - // files.ls - { - name: 'should ls directory', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' - }, - { - name: 'should ls -l directory', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/7' - }, - // files.read*Stream - { - name: 'readPullStream', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' - }, - { - name: 'readReadableStream', - reason: 'FIXME: https://github.com/ipfs/js-ipfs-mfs/issues/8' - } - ] - }) + tests.files(defaultCommonFactory) tests.key(CommonFactory.create({ spawnOptions: { From 4a72e23b7476b84c062ed066264f3847535c61df Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 11:45:08 +0100 Subject: [PATCH 25/51] fix: consistent badge style in docs (#1494) Super important PR. I couldn't let this travesty continue. Jenkins doesn't support flat-square so I've switched all the badges to using the flat style so they all have the same style. Phew. License: MIT Signed-off-by: Alan Shaw --- README.md | 80 ++++++++++---------- scripts/generate-package-table-for-readme.js | 6 +- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 36c16e7ada..cc705a8a34 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@

The JavaScript implementation of the IPFS protocol.

- - - -
+ + + +

@@ -18,10 +18,10 @@

- - - - + + + +

@@ -765,45 +765,45 @@ Please use the same script if you need to update this table. | Package | Version | Deps | CI | Coverage | | ---------|---------|---------|---------|--------- | | **Files** | -| [`ipfs-unixfs-engine`](//github.com/ipfs/js-ipfs-unixfs-engine) | [![npm](https://img.shields.io/npm/v/ipfs-unixfs-engine.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-unixfs-engine/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-unixfs-engine.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-unixfs-engine) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-unixfs-engine/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-unixfs-engine/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-unixfs-engine/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-unixfs-engine) | +| [`ipfs-unixfs-engine`](//github.com/ipfs/js-ipfs-unixfs-engine) | [![npm](https://img.shields.io/npm/v/ipfs-unixfs-engine.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-unixfs-engine/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-unixfs-engine.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-unixfs-engine) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-unixfs-engine/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-unixfs-engine/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-unixfs-engine/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-unixfs-engine) | | **DAG** | -| [`ipld`](//github.com/ipld/js-ipld) | [![npm](https://img.shields.io/npm/v/ipld.svg?maxAge=86400&style=flat-square)](//github.com/ipld/js-ipld/releases) | [![Dep](https://david-dm.org/ipld/js-ipld.svg?style=flat-square)](https://david-dm.org/ipld/js-ipld) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipld/js-ipld/master)](https://ci.ipfs.team/job/ipld/job/js-ipld/job/master/) | [![Coverage Status](https://codecov.io/gh/ipld/js-ipld/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/js-ipld) | -| [`ipld-dag-pb`](//github.com/ipld/js-ipld-dag-pb) | [![npm](https://img.shields.io/npm/v/ipld-dag-pb.svg?maxAge=86400&style=flat-square)](//github.com/ipld/js-ipld-dag-pb/releases) | [![Dep](https://david-dm.org/ipld/js-ipld-dag-pb.svg?style=flat-square)](https://david-dm.org/ipld/js-ipld-dag-pb) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipld/js-ipld-dag-pb/master)](https://ci.ipfs.team/job/ipld/job/js-ipld-dag-pb/job/master/) | [![Coverage Status](https://codecov.io/gh/ipld/js-ipld-dag-pb/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/js-ipld-dag-pb) | -| [`ipld-dag-cbor`](//github.com/ipld/js-ipld-dag-cbor) | [![npm](https://img.shields.io/npm/v/ipld-dag-cbor.svg?maxAge=86400&style=flat-square)](//github.com/ipld/js-ipld-dag-cbor/releases) | [![Dep](https://david-dm.org/ipld/js-ipld-dag-cbor.svg?style=flat-square)](https://david-dm.org/ipld/js-ipld-dag-cbor) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipld/js-ipld-dag-cbor/master)](https://ci.ipfs.team/job/ipld/job/js-ipld-dag-cbor/job/master/) | [![Coverage Status](https://codecov.io/gh/ipld/js-ipld-dag-cbor/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/js-ipld-dag-cbor) | +| [`ipld`](//github.com/ipld/js-ipld) | [![npm](https://img.shields.io/npm/v/ipld.svg?maxAge=86400&style=flat)](//github.com/ipld/js-ipld/releases) | [![Dep](https://david-dm.org/ipld/js-ipld.svg?style=flat)](https://david-dm.org/ipld/js-ipld) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipld/js-ipld/master)](https://ci.ipfs.team/job/ipld/job/js-ipld/job/master/) | [![Coverage Status](https://codecov.io/gh/ipld/js-ipld/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/js-ipld) | +| [`ipld-dag-pb`](//github.com/ipld/js-ipld-dag-pb) | [![npm](https://img.shields.io/npm/v/ipld-dag-pb.svg?maxAge=86400&style=flat)](//github.com/ipld/js-ipld-dag-pb/releases) | [![Dep](https://david-dm.org/ipld/js-ipld-dag-pb.svg?style=flat)](https://david-dm.org/ipld/js-ipld-dag-pb) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipld/js-ipld-dag-pb/master)](https://ci.ipfs.team/job/ipld/job/js-ipld-dag-pb/job/master/) | [![Coverage Status](https://codecov.io/gh/ipld/js-ipld-dag-pb/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/js-ipld-dag-pb) | +| [`ipld-dag-cbor`](//github.com/ipld/js-ipld-dag-cbor) | [![npm](https://img.shields.io/npm/v/ipld-dag-cbor.svg?maxAge=86400&style=flat)](//github.com/ipld/js-ipld-dag-cbor/releases) | [![Dep](https://david-dm.org/ipld/js-ipld-dag-cbor.svg?style=flat)](https://david-dm.org/ipld/js-ipld-dag-cbor) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipld/js-ipld-dag-cbor/master)](https://ci.ipfs.team/job/ipld/job/js-ipld-dag-cbor/job/master/) | [![Coverage Status](https://codecov.io/gh/ipld/js-ipld-dag-cbor/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/js-ipld-dag-cbor) | | **Repo** | -| [`ipfs-repo`](//github.com/ipfs/js-ipfs-repo) | [![npm](https://img.shields.io/npm/v/ipfs-repo.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-repo/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-repo.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-repo) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-repo/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-repo/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-repo/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-repo) | +| [`ipfs-repo`](//github.com/ipfs/js-ipfs-repo) | [![npm](https://img.shields.io/npm/v/ipfs-repo.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-repo/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-repo.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-repo) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-repo/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-repo/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-repo/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-repo) | | **Exchange** | -| [`ipfs-block-service`](//github.com/ipfs/js-ipfs-block-service) | [![npm](https://img.shields.io/npm/v/ipfs-block-service.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-block-service/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-block-service.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-block-service) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-block-service/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-block-service/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-block-service/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-block-service) | -| [`ipfs-bitswap`](//github.com/ipfs/js-ipfs-bitswap) | [![npm](https://img.shields.io/npm/v/ipfs-bitswap.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-bitswap/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-bitswap.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-bitswap) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-bitswap/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-bitswap/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-bitswap/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-bitswap) | +| [`ipfs-block-service`](//github.com/ipfs/js-ipfs-block-service) | [![npm](https://img.shields.io/npm/v/ipfs-block-service.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-block-service/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-block-service.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-block-service) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-block-service/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-block-service/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-block-service/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-block-service) | +| [`ipfs-bitswap`](//github.com/ipfs/js-ipfs-bitswap) | [![npm](https://img.shields.io/npm/v/ipfs-bitswap.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-bitswap/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-bitswap.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-bitswap) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-bitswap/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-bitswap/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-bitswap/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-bitswap) | | **libp2p** | -| [`libp2p`](//github.com/libp2p/js-libp2p) | [![npm](https://img.shields.io/npm/v/libp2p.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p) | -| [`libp2p-circuit`](//github.com/libp2p/js-libp2p-circuit) | [![npm](https://img.shields.io/npm/v/libp2p-circuit.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-circuit/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-circuit.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-circuit) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-circuit/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-circuit/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-circuit/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-circuit) | -| [`libp2p-floodsub`](//github.com/libp2p/js-libp2p-floodsub) | [![npm](https://img.shields.io/npm/v/libp2p-floodsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-floodsub/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-floodsub) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-floodsub/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-floodsub/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-floodsub/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-floodsub) | -| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-kad-dht/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-kad-dht/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | -| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [![npm](https://img.shields.io/npm/v/libp2p-mdns.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mdns/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-mdns.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mdns) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-mdns/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-mdns/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-mdns/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-mdns) | -| [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-mplex/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-mplex/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | -| [`libp2p-railing`](//github.com/libp2p/js-libp2p-railing) | [![npm](https://img.shields.io/npm/v/libp2p-railing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-railing/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-railing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-railing) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-railing/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-railing/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-railing/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-railing) | -| [`libp2p-secio`](//github.com/libp2p/js-libp2p-secio) | [![npm](https://img.shields.io/npm/v/libp2p-secio.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-secio/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-secio/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-secio/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-secio/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-secio) | -| [`libp2p-tcp`](//github.com/libp2p/js-libp2p-tcp) | [![npm](https://img.shields.io/npm/v/libp2p-tcp.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-tcp/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-tcp) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-tcp/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-tcp/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-tcp/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-tcp) | -| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-webrtc-star/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-webrtc-star/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | -| [`libp2p-websocket-star`](//github.com/libp2p/js-libp2p-websocket-star) | [![npm](https://img.shields.io/npm/v/libp2p-websocket-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websocket-star/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-websocket-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websocket-star) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-websocket-star/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-websocket-star/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-websocket-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star) | -| [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-websockets/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-websockets/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | +| [`libp2p`](//github.com/libp2p/js-libp2p) | [![npm](https://img.shields.io/npm/v/libp2p.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p) | +| [`libp2p-circuit`](//github.com/libp2p/js-libp2p-circuit) | [![npm](https://img.shields.io/npm/v/libp2p-circuit.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-circuit/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-circuit.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-circuit) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-circuit/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-circuit/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-circuit/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-circuit) | +| [`libp2p-floodsub`](//github.com/libp2p/js-libp2p-floodsub) | [![npm](https://img.shields.io/npm/v/libp2p-floodsub.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-floodsub/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-floodsub) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-floodsub/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-floodsub/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-floodsub/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-floodsub) | +| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-kad-dht/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-kad-dht/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | +| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [![npm](https://img.shields.io/npm/v/libp2p-mdns.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-mdns/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-mdns.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-mdns) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-mdns/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-mdns/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-mdns/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-mdns) | +| [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-mplex/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-mplex/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | +| [`libp2p-railing`](//github.com/libp2p/js-libp2p-railing) | [![npm](https://img.shields.io/npm/v/libp2p-railing.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-railing/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-railing.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-railing) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-railing/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-railing/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-railing/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-railing) | +| [`libp2p-secio`](//github.com/libp2p/js-libp2p-secio) | [![npm](https://img.shields.io/npm/v/libp2p-secio.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-secio/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-secio) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-secio/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-secio/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-secio/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-secio) | +| [`libp2p-tcp`](//github.com/libp2p/js-libp2p-tcp) | [![npm](https://img.shields.io/npm/v/libp2p-tcp.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-tcp/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-tcp.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-tcp) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-tcp/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-tcp/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-tcp/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-tcp) | +| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-webrtc-star/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-webrtc-star/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | +| [`libp2p-websocket-star`](//github.com/libp2p/js-libp2p-websocket-star) | [![npm](https://img.shields.io/npm/v/libp2p-websocket-star.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-websocket-star/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-websocket-star.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-websocket-star) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-websocket-star/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-websocket-star/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-websocket-star/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websocket-star) | +| [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-websockets/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-websockets/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | | **Data Types** | -| [`ipfs-block`](//github.com/ipfs/js-ipfs-block) | [![npm](https://img.shields.io/npm/v/ipfs-block.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-block/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-block.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-block) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-block/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-block/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-block/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-block) | -| [`ipfs-unixfs`](//github.com/ipfs/js-ipfs-unixfs) | [![npm](https://img.shields.io/npm/v/ipfs-unixfs.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-unixfs/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-unixfs.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-unixfs) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-unixfs/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-unixfs/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-unixfs/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-unixfs) | -| [`peer-id`](//github.com/libp2p/js-peer-id) | [![npm](https://img.shields.io/npm/v/peer-id.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-id/releases) | [![Dep](https://david-dm.org/libp2p/js-peer-id.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-id) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-peer-id/master)](https://ci.ipfs.team/job/libp2p/job/js-peer-id/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-peer-id/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-id) | -| [`peer-info`](//github.com/libp2p/js-peer-info) | [![npm](https://img.shields.io/npm/v/peer-info.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-info/releases) | [![Dep](https://david-dm.org/libp2p/js-peer-info.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-info) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-peer-info/master)](https://ci.ipfs.team/job/libp2p/job/js-peer-info/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-peer-info/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-info) | -| [`multiaddr`](//github.com/multiformats/js-multiaddr) | [![npm](https://img.shields.io/npm/v/multiaddr.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-multiaddr/releases) | [![Dep](https://david-dm.org/multiformats/js-multiaddr.svg?style=flat-square)](https://david-dm.org/multiformats/js-multiaddr) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-multiaddr/master)](https://ci.ipfs.team/job/multiformats/job/js-multiaddr/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-multiaddr/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-multiaddr) | -| [`multihashes`](//github.com/multiformats/js-multihash) | [![npm](https://img.shields.io/npm/v/multihashes.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-multihash/releases) | [![Dep](https://david-dm.org/multiformats/js-multihash.svg?style=flat-square)](https://david-dm.org/multiformats/js-multihash) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-multihash/master)](https://ci.ipfs.team/job/multiformats/job/js-multihash/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-multihash/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-multihash) | +| [`ipfs-block`](//github.com/ipfs/js-ipfs-block) | [![npm](https://img.shields.io/npm/v/ipfs-block.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-block/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-block.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-block) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-block/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-block/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-block/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-block) | +| [`ipfs-unixfs`](//github.com/ipfs/js-ipfs-unixfs) | [![npm](https://img.shields.io/npm/v/ipfs-unixfs.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-unixfs/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-unixfs.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-unixfs) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-unixfs/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-unixfs/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-unixfs/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-unixfs) | +| [`peer-id`](//github.com/libp2p/js-peer-id) | [![npm](https://img.shields.io/npm/v/peer-id.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-peer-id/releases) | [![Dep](https://david-dm.org/libp2p/js-peer-id.svg?style=flat)](https://david-dm.org/libp2p/js-peer-id) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-peer-id/master)](https://ci.ipfs.team/job/libp2p/job/js-peer-id/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-peer-id/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-id) | +| [`peer-info`](//github.com/libp2p/js-peer-info) | [![npm](https://img.shields.io/npm/v/peer-info.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-peer-info/releases) | [![Dep](https://david-dm.org/libp2p/js-peer-info.svg?style=flat)](https://david-dm.org/libp2p/js-peer-info) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-peer-info/master)](https://ci.ipfs.team/job/libp2p/job/js-peer-info/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-peer-info/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-peer-info) | +| [`multiaddr`](//github.com/multiformats/js-multiaddr) | [![npm](https://img.shields.io/npm/v/multiaddr.svg?maxAge=86400&style=flat)](//github.com/multiformats/js-multiaddr/releases) | [![Dep](https://david-dm.org/multiformats/js-multiaddr.svg?style=flat)](https://david-dm.org/multiformats/js-multiaddr) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-multiaddr/master)](https://ci.ipfs.team/job/multiformats/job/js-multiaddr/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-multiaddr/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-multiaddr) | +| [`multihashes`](//github.com/multiformats/js-multihash) | [![npm](https://img.shields.io/npm/v/multihashes.svg?maxAge=86400&style=flat)](//github.com/multiformats/js-multihash/releases) | [![Dep](https://david-dm.org/multiformats/js-multihash.svg?style=flat)](https://david-dm.org/multiformats/js-multihash) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-multihash/master)](https://ci.ipfs.team/job/multiformats/job/js-multihash/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-multihash/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-multihash) | | **Crypto** | -| [`libp2p-crypto`](//github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-crypto/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-crypto/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | -| [`libp2p-keychain`](//github.com/libp2p/js-libp2p-keychain) | [![npm](https://img.shields.io/npm/v/libp2p-keychain.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-keychain/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-keychain) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-keychain/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-keychain/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-keychain/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-keychain) | +| [`libp2p-crypto`](//github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-crypto/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-crypto/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-crypto) | +| [`libp2p-keychain`](//github.com/libp2p/js-libp2p-keychain) | [![npm](https://img.shields.io/npm/v/libp2p-keychain.svg?maxAge=86400&style=flat)](//github.com/libp2p/js-libp2p-keychain/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-keychain.svg?style=flat)](https://david-dm.org/libp2p/js-libp2p-keychain) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=libp2p/js-libp2p-keychain/master)](https://ci.ipfs.team/job/libp2p/job/js-libp2p-keychain/job/master/) | [![Coverage Status](https://codecov.io/gh/libp2p/js-libp2p-keychain/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/js-libp2p-keychain) | | **Generics/Utils** | -| [`ipfs-api`](//github.com/ipfs/js-ipfs-api) | [![npm](https://img.shields.io/npm/v/ipfs-api.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/js-ipfs-api/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-api.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-api) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-api/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-api/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-api/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-api) | -| [`ipfs-multipart`](//github.com/ipfs/ipfs-multipart) | [![npm](https://img.shields.io/npm/v/ipfs-multipart.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/ipfs-multipart/releases) | [![Dep](https://david-dm.org/ipfs/ipfs-multipart.svg?style=flat-square)](https://david-dm.org/ipfs/ipfs-multipart) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/ipfs-multipart/master)](https://ci.ipfs.team/job/ipfs/job/ipfs-multipart/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/ipfs-multipart/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/ipfs-multipart) | -| [`is-ipfs`](//github.com/ipfs/is-ipfs) | [![npm](https://img.shields.io/npm/v/is-ipfs.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/is-ipfs/releases) | [![Dep](https://david-dm.org/ipfs/is-ipfs.svg?style=flat-square)](https://david-dm.org/ipfs/is-ipfs) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/is-ipfs/master)](https://ci.ipfs.team/job/ipfs/job/is-ipfs/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/is-ipfs/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/is-ipfs) | -| [`multihashing`](//github.com/multiformats/js-multihashing) | [![npm](https://img.shields.io/npm/v/multihashing.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-multihashing/releases) | [![Dep](https://david-dm.org/multiformats/js-multihashing.svg?style=flat-square)](https://david-dm.org/multiformats/js-multihashing) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-multihashing/master)](https://ci.ipfs.team/job/multiformats/job/js-multihashing/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-multihashing/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-multihashing) | -| [`mafmt`](//github.com/multiformats/js-mafmt) | [![npm](https://img.shields.io/npm/v/mafmt.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-mafmt/releases) | [![Dep](https://david-dm.org/multiformats/js-mafmt.svg?style=flat-square)](https://david-dm.org/multiformats/js-mafmt) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-mafmt/master)](https://ci.ipfs.team/job/multiformats/job/js-mafmt/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-mafmt/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-mafmt) | +| [`ipfs-api`](//github.com/ipfs/js-ipfs-api) | [![npm](https://img.shields.io/npm/v/ipfs-api.svg?maxAge=86400&style=flat)](//github.com/ipfs/js-ipfs-api/releases) | [![Dep](https://david-dm.org/ipfs/js-ipfs-api.svg?style=flat)](https://david-dm.org/ipfs/js-ipfs-api) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/js-ipfs-api/master)](https://ci.ipfs.team/job/ipfs/job/js-ipfs-api/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/js-ipfs-api/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-api) | +| [`ipfs-multipart`](//github.com/ipfs/ipfs-multipart) | [![npm](https://img.shields.io/npm/v/ipfs-multipart.svg?maxAge=86400&style=flat)](//github.com/ipfs/ipfs-multipart/releases) | [![Dep](https://david-dm.org/ipfs/ipfs-multipart.svg?style=flat)](https://david-dm.org/ipfs/ipfs-multipart) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/ipfs-multipart/master)](https://ci.ipfs.team/job/ipfs/job/ipfs-multipart/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/ipfs-multipart/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/ipfs-multipart) | +| [`is-ipfs`](//github.com/ipfs/is-ipfs) | [![npm](https://img.shields.io/npm/v/is-ipfs.svg?maxAge=86400&style=flat)](//github.com/ipfs/is-ipfs/releases) | [![Dep](https://david-dm.org/ipfs/is-ipfs.svg?style=flat)](https://david-dm.org/ipfs/is-ipfs) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=ipfs/is-ipfs/master)](https://ci.ipfs.team/job/ipfs/job/is-ipfs/job/master/) | [![Coverage Status](https://codecov.io/gh/ipfs/is-ipfs/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/is-ipfs) | +| [`multihashing`](//github.com/multiformats/js-multihashing) | [![npm](https://img.shields.io/npm/v/multihashing.svg?maxAge=86400&style=flat)](//github.com/multiformats/js-multihashing/releases) | [![Dep](https://david-dm.org/multiformats/js-multihashing.svg?style=flat)](https://david-dm.org/multiformats/js-multihashing) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-multihashing/master)](https://ci.ipfs.team/job/multiformats/job/js-multihashing/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-multihashing/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-multihashing) | +| [`mafmt`](//github.com/multiformats/js-mafmt) | [![npm](https://img.shields.io/npm/v/mafmt.svg?maxAge=86400&style=flat)](//github.com/multiformats/js-mafmt/releases) | [![Dep](https://david-dm.org/multiformats/js-mafmt.svg?style=flat)](https://david-dm.org/multiformats/js-mafmt) | [![Build Status](https://ci.ipfs.team/buildStatus/icon?job=multiformats/js-mafmt/master)](https://ci.ipfs.team/job/multiformats/job/js-mafmt/job/master/) | [![Coverage Status](https://codecov.io/gh/multiformats/js-mafmt/branch/master/graph/badge.svg)](https://codecov.io/gh/multiformats/js-mafmt) | ## Development diff --git a/scripts/generate-package-table-for-readme.js b/scripts/generate-package-table-for-readme.js index 2a9754599e..a5252cb122 100755 --- a/scripts/generate-package-table-for-readme.js +++ b/scripts/generate-package-table-for-readme.js @@ -54,7 +54,7 @@ const rows = [ 'Crypto', ['libp2p/js-libp2p-crypto', 'libp2p-crypto'], ['libp2p/js-libp2p-keychain', 'libp2p-keychain'], - + 'Generics/Utils', ['ipfs/js-ipfs-api', 'ipfs-api'], ['ipfs/ipfs-multipart', 'ipfs-multipart'], @@ -71,9 +71,9 @@ const packageBadges = [ // Package (gh, npm) => `[\`${npm}\`](//github.com/${gh})`, // Version - (gh, npm) => `[![npm](https://img.shields.io/npm/v/${npm}.svg?maxAge=86400&style=flat-square)](//github.com/${gh}/releases)`, + (gh, npm) => `[![npm](https://img.shields.io/npm/v/${npm}.svg?maxAge=86400&style=flat)](//github.com/${gh}/releases)`, // Deps - (gh, npm) => `[![Deps](https://david-dm.org/${gh}.svg?style=flat-square)](https://david-dm.org/${gh})`, + (gh, npm) => `[![Deps](https://david-dm.org/${gh}.svg?style=flat)](https://david-dm.org/${gh})`, // CI (gh, npm) => { // Need to fix the path for jenkins links, as jenkins adds `/job/` between everything From 213abb31def829f81d8c0bea5af5cb9526cacb44 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 11:53:31 +0100 Subject: [PATCH 26/51] chore: update contributors From 57f977fd21e09d274d548e96417f37c516186fbf Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 11:53:32 +0100 Subject: [PATCH 27/51] chore: release version v0.31.4 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15687f2848..65a2ad0e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +## [0.31.4](https://github.com/ipfs/js-ipfs/compare/v0.31.3...v0.31.4) (2018-08-09) + + +### Bug Fixes + +* consistent badge style in docs ([#1494](https://github.com/ipfs/js-ipfs/issues/1494)) ([4a72e23](https://github.com/ipfs/js-ipfs/commit/4a72e23)) +* files.ls and files.read*Stream tests ([#1493](https://github.com/ipfs/js-ipfs/issues/1493)) ([a0bc79b](https://github.com/ipfs/js-ipfs/commit/a0bc79b)) + + + ## [0.31.3](https://github.com/ipfs/js-ipfs/compare/v0.31.2...v0.31.3) (2018-08-09) diff --git a/package.json b/package.json index a603cc1b9e..ffcc9fa90e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.3", + "version": "0.31.4", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From 80ae33eb90d0ae228f13fd746c5b5d21324f47e8 Mon Sep 17 00:00:00 2001 From: David Gilbertson Date: Thu, 9 Aug 2018 21:19:03 +1000 Subject: [PATCH 28/51] docs: use async/await in 101 example (#1491) (#1495) Replaces the async npm package with vanilla JavaScript. License: MIT Signed-off-by: David Gilbertson --- README.md | 4 +- examples/ipfs-101/1.js | 38 ++++++--------- examples/ipfs-101/README.md | 89 +++++++++++++++++++--------------- examples/ipfs-101/package.json | 1 - 4 files changed, 67 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index cc705a8a34..93b553bc0c 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,9 @@ This project is available through [npm](https://www.npmjs.com/). To install run > npm install ipfs --save ``` -Requires npm@3 and node@6 or above, tested on OSX & Linux, expected to work on Windows. +We support both the Current and Active LTS versions of Node.js. Please see [nodejs.org](https://nodejs.org/) for what these currently are. + +This project is tested on OSX & Linux, expected to work on Windows. ### Use in Node.js diff --git a/examples/ipfs-101/1.js b/examples/ipfs-101/1.js index 9f206d543a..664af3d90c 100644 --- a/examples/ipfs-101/1.js +++ b/examples/ipfs-101/1.js @@ -1,32 +1,22 @@ 'use strict' -const series = require('async/series') const IPFS = require('ipfs') const node = new IPFS() -let fileMultihash - -series([ - (cb) => node.on('ready', cb), - (cb) => node.version((err, version) => { - if (err) { return cb(err) } - console.log('Version:', version.version) - cb() - }), - (cb) => node.files.add({ + +node.on('ready', async () => { + const version = await node.version() + + console.log('Version:', version.version) + + const filesAdded = await node.files.add({ path: 'hello.txt', content: Buffer.from('Hello World 101') - }, (err, filesAdded) => { - if (err) { return cb(err) } - - console.log('\nAdded file:', filesAdded[0].path, filesAdded[0].hash) - fileMultihash = filesAdded[0].hash - cb() - }), - (cb) => node.files.cat(fileMultihash, (err, data) => { - if (err) { return cb(err) } - - console.log('\nFile content:') - process.stdout.write(data) }) -]) + + console.log('Added file:', filesAdded[0].path, filesAdded[0].hash) + + const fileBuffer = await node.files.cat(filesAdded[0].hash) + + console.log('Added file contents:', fileBuffer.toString()) +}) diff --git a/examples/ipfs-101/README.md b/examples/ipfs-101/README.md index df44debe7e..98e969f534 100644 --- a/examples/ipfs-101/README.md +++ b/examples/ipfs-101/README.md @@ -1,80 +1,91 @@ # IPFS 101, spawn a node and add a file to the IPFS network -In this tutorial, we go through spawning an IPFS node, adding a file and cat'ing the file multihash locally and throught the gateway. +In this tutorial, we go through spawning an IPFS node, adding a file and cat'ing the file multihash locally and through the gateway. -You can find a complete version of this tutorial in [1.js](./1.js). For this tutorial, you need to install the following dependencies: `ipfs` and `async` using `npm install ipfs async`. +You can find a complete version of this tutorial in [1.js](./1.js). For this tutorial, you need to install `ipfs` using `npm install ipfs`. Creating an IPFS instance can be done in one line, after requiring the module, you simply have to: -```JavaScript +```js const IPFS = require('ipfs') const node = new IPFS() ``` -We can listen for the `ready` event to learn when the node is ready to be used. In this part, we start using `async/series` to help us manage the async flow. As a test, we are going to check the version of the node. +We can listen for the `ready` event to learn when the node is ready to be used. Within the ready event, we'll use `async`/`await` to help us manage the async flow. -```JavaScript +As a test, we are going to check the version of the node. + +```js const IPFS = require('ipfs') const node = new IPFS() -series([ - (cb) => node.on('ready', cb), - (cb) => node.version((err, version) => { - if (err) { return cb(err) } - console.log('Version:', version.version) - cb() - }) -]) +node.on('ready', async () => { + const version = await node.version() + + console.log('Version:', version.version) +}) ``` +(If you prefer not to use `async`/`await`, you can instead use `.then()` as you would with any promise, +or pass an [error-first callback](https://nodejs.org/api/errors.html#errors_error_first_callbacks), e.g. `node.version((err, version) => { ... })`) + Running the code above gets you: ```bash > node 1.js -IPFS Version: 0.25.0 +Version: 0.31.2 ``` -Now lets make it more interesting and add a file to IPFS. We can do it by adding another async call to the series that uses the `node.files.add` call. You can learn about IPFS API for files at [interface-ipfs-core](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md). - -```JavaScript -// Create the File to add, a file consists of a path + content. More details on -// https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md -(cb) => node.files.add({ - path: 'hello.txt', - content: Buffer.from('Hello World') -}, (err, filesAdded) => { - if (err) { return cb(err) } - - // Once the file is added, we get back an object containing the path, the - // multihash and the sie of the file - console.log('\nAdded file:', filesAdded[0].path, filesAdded[0].hash) - fileMultihash = filesAdded[0].hash - cb() +Now let's make it more interesting and add a file to IPFS using `node.files.add`. A file consists of a path and content. + +You can learn about the IPFS File API at [interface-ipfs-core](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md). + +```js +node.on('ready', async () => { + const version = await node.version() + + console.log('Version:', version.version) + + const filesAdded = await node.files.add({ + path: 'hello.txt', + content: Buffer.from('Hello World 101') + }) + + console.log('Added file:', filesAdded[0].path, filesAdded[0].hash) }) ``` -If you avoid calling that last `cb()`, the program won't exit enabling you to go to an IPFS Gateway and load the printed hash from a gateway. Go ahead and try it! +You can now go to an IPFS Gateway and load the printed hash from a gateway. Go ahead and try it! ```bash > node 1.js -Version: 0.25.0 +Version: 0.31.2 Added file: hello.txt QmXgZAUWd8yo4tvjBETqzUy3wLx5YRzuDwUQnBwRGrAmAo # Copy that hash and load it on the gateway, here is a prefiled url: # https://ipfs.io/ipfs/QmXgZAUWd8yo4tvjBETqzUy3wLx5YRzuDwUQnBwRGrAmAo ``` -The last step of this tutorial is retrieving the file back using the `cat` 😺 call. Add another step on the series chain that does the following: +The last step of this tutorial is retrieving the file back using the `cat` 😺 call. + +```js +node.on('ready', async () => { + const version = await node.version() + + console.log('Version:', version.version) + + const filesAdded = await node.files.add({ + path: 'hello.txt', + content: Buffer.from('Hello World 101') + }) + + console.log('Added file:', filesAdded[0].path, filesAdded[0].hash) -```JavaScript -(cb) => node.files.cat(fileMultihash, (err, data) => { - if (err) { return cb(err) } + const fileBuffer = await node.files.cat(filesAdded[0].hash) - console.log('\nFile content:') - // print the file to the terminal and then exit the program - process.stdout.write(data) + console.log('Added file contents:', fileBuffer.toString()) }) ``` diff --git a/examples/ipfs-101/package.json b/examples/ipfs-101/package.json index 4973b2c618..7b77539945 100644 --- a/examples/ipfs-101/package.json +++ b/examples/ipfs-101/package.json @@ -9,7 +9,6 @@ "author": "David Dias ", "license": "MIT", "dependencies": { - "async": "^2.6.0", "ipfs": "file:../../" } } From 5cde7c1df9110f0f9754ee29a4e96195728f239e Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 9 Aug 2018 16:19:05 +0100 Subject: [PATCH 29/51] fix: add missing space after emoji License: MIT Signed-off-by: Alan Shaw --- RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index 576ce9a6fe..bfd2ecb52e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -31,7 +31,7 @@ - [ ] Reddit - [ ] Blog post -# 🙌🏽Want to contribute? +# 🙌🏽 Want to contribute? Would you like to contribute to the IPFS project and don't know how? Well, there are a few places you can get started: From 31097fe4906d8b5bbabb9bfdeccd8ee0b43cb812 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 9 Aug 2018 17:55:19 +0100 Subject: [PATCH 30/51] chore: upgrade ipfs-repo (#1496) --- package.json | 2 +- test/fixtures/go-ipfs-repo/version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ffcc9fa90e..3cd1773ac3 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "ipfs-http-response": "~0.1.2", "ipfs-mfs": "~0.3.0", "ipfs-multipart": "~0.1.0", - "ipfs-repo": "~0.22.1", + "ipfs-repo": "~0.23.1", "ipfs-unixfs": "~0.1.15", "ipfs-unixfs-engine": "~0.32.1", "ipld": "~0.17.3", diff --git a/test/fixtures/go-ipfs-repo/version b/test/fixtures/go-ipfs-repo/version index 62f9457511..c7930257df 100644 --- a/test/fixtures/go-ipfs-repo/version +++ b/test/fixtures/go-ipfs-repo/version @@ -1 +1 @@ -6 \ No newline at end of file +7 \ No newline at end of file From 661c0bc18e7d7d0b9d39089db6851e832abe7d9f Mon Sep 17 00:00:00 2001 From: David Dias Date: Sat, 11 Aug 2018 13:08:32 +0200 Subject: [PATCH 31/51] docs: Moved to ipfs/pm See https://github.com/ipfs/pm/blob/master/JS_CORE_DEV_MGMT.md --- MGMT.md | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 MGMT.md diff --git a/MGMT.md b/MGMT.md deleted file mode 100644 index 35c508f91d..0000000000 --- a/MGMT.md +++ /dev/null @@ -1,36 +0,0 @@ -# Core Dev Team Work Tracking & Managment - -## How work gets organized (a tl;dr;) - -The js-ipfs core working group follows the OKR structure established for the IPFS project to set the quarterly targets. Within each quarter, work gets tracked using Github and Waffle. - -- Github is used for discussions and track current endeavours. -- Waffle gives us a [Kanban](https://en.wikipedia.org/wiki/Kanban) view over the work at hand. - -![](https://ipfs.io/ipfs/QmWNd86qtjyFnygSAHkZDy4fUB1WnRa4WNt8gt1rSiq7of) - -In the Waffle board, we have 4 columns: - -- **Inbox** - New issues or PRs that haven't been evaluated yet -- **Backlog** - Issues that are blocked or discussion threads that are not currently active -- **Ready** - Issues Ready to be worked on -- **In Progress** - Issues that someone is already tackling. Contributors should focus on a few things rather than many at once. -- **Done** - Issues are automatically moved here when the issue is closed or the PR merged. - -We track work for the JavaScript implementation of the IPFS protocol in 3 separate waffle boards: - -- [js-ipfs](http://waffle.io/ipfs/js-ipfs) -- [js-libp2p](http://waffle.io/libp2p/js-libp2p) -- [js-ipld](http://waffle.io/ipld/js-ipld) - -## Issue labels and how to use filters - -We use labels to tag urgency and the difficulty of an issue. The current label system has: - -- `difficulty:{easy, moderate, hard}` - This is an instinctive measure give by the project lead or leads. It is a subjective best guess, however the current golden rule is that an issue with difficulty:easy should not require more than a morning (3~4 hours) to do and it should not require having to mess with multiple modules to complete. Issues with difficulty moderate or hard might require some discussion around the problem or even request that another team (i.e go-ipfs) makes some changes. The length of moderate or hard issue might be a day to ad-aeternum. -- `priority (P0, P1, P2, P3, P4)` - P0 is the most important while P4 is the least. -- `help wanted` - Issues perfect for new contributors. They will have the information necessary or the pointers for a new contributor to figure out what is required. These issues are never blocked on some other issue be done first. - -## Weekly Core Dev Team Calls - -[⚡️ⒿⓈ Core Dev Team Weekly Sync 🙌🏽](https://github.com/ipfs/pm/issues/650) From 59bc6d5c9a217a2b2c535cce6d0aac2f0b40ea79 Mon Sep 17 00:00:00 2001 From: Volker Mische Date: Mon, 13 Aug 2018 14:16:51 +0200 Subject: [PATCH 32/51] docs: improve start example (#1501) The `start` example was missing the `ready` event. The example is now more similar to the `stop` example. --- README.md | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 93b553bc0c..2a363398cb 100644 --- a/README.md +++ b/README.md @@ -361,24 +361,28 @@ This method is asynchronous. There are several ways to be notified when the node ```js const node = new IPFS({ start: false }) -// Use a promise: -node.start() - .then(() => console.log('Node started!')) - .catch(error => console.error('Node failed to start!', error)) - -// OR use a callback: -node.start(error => { - if (error) { - console.error('Node failed to start!', error) - return - } - console.log('Node started!') -}) +node.on('ready', () => { + console.log('Node is ready to use!') + + // Use a promise: + node.start() + .then(() => console.log('Node started!')) + .catch(error => console.error('Node failed to start!', error)) + + // OR use a callback: + node.start(error => { + if (error) { + console.error('Node failed to start!', error) + return + } + console.log('Node started!') + }) -// OR use events: -node.on('error', error => console.error('Something went terribly wrong!', error)) -node.on('start', () => console.log('Node started!')) -node.start() + // OR use events: + node.on('error', error => console.error('Something went terribly wrong!', error)) + node.on('start', () => console.log('Node started!')) + node.start() +}) ``` #### `node.stop([callback])` From afd32553b407c8e5e456b72e5f1bf5401d3a2ca6 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 15 Aug 2018 16:20:31 +0100 Subject: [PATCH 33/51] fix: object.patch.rmLink not working (#1508) This PR contains fixes for the test failures that are happening in master for `object.patch.rmLink`. I **think** this change https://github.com/ipld/js-ipld-dag-pb/pull/80 made `DAGLink` validate the hash better and caused the failures to start. It also contains fixes for the `pinSet` module which was using the "private" `_multihash` property which was removed in https://github.com/ipld/js-ipld-dag-pb/pull/81 and released 2 days ago - don't use private APIs kids. License: MIT Signed-off-by: Alan Shaw --- src/cli/commands/object/patch/rm-link.js | 8 +------- src/core/components/pin-set.js | 2 +- src/http/api/resources/object.js | 6 +++--- test/core/pin-set.js | 4 ++-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/cli/commands/object/patch/rm-link.js b/src/cli/commands/object/patch/rm-link.js index cac1666ce3..dde69c30f8 100644 --- a/src/cli/commands/object/patch/rm-link.js +++ b/src/cli/commands/object/patch/rm-link.js @@ -1,6 +1,5 @@ 'use strict' -const DAGLink = require('ipld-dag-pb').DAGLink const debug = require('debug') const log = debug('cli:object') log.error = debug('cli:object:error') @@ -14,12 +13,7 @@ module.exports = { builder: {}, handler (argv) { - // TODO rmLink should support removing by name and/or multihash - // without having to know everything, which in fact it does, however, - // since it expectes a DAGLink type, we have to pass some fake size and - // hash. - const link = new DAGLink(argv.link, 1, 'Qm') - argv.ipfs.object.patch.rmLink(argv.root, link, { + argv.ipfs.object.patch.rmLink(argv.root, { name: argv.link }, { enc: 'base58' }, (err, node) => { if (err) { diff --git a/src/core/components/pin-set.js b/src/core/components/pin-set.js index 1c816d993c..d100b85311 100644 --- a/src/core/components/pin-set.js +++ b/src/core/components/pin-set.js @@ -168,7 +168,7 @@ exports = module.exports = function (dag) { function storeChild (err, child, binIdx, cb) { if (err) { return cb(err) } - const opts = { cid: new CID(child._multihash), preload: false } + const opts = { cid: new CID(child.multihash), preload: false } dag.put(child, opts, err => { if (err) { return cb(err) } fanoutLinks[binIdx] = new DAGLink('', child.size, child.multihash) diff --git a/src/http/api/resources/object.js b/src/http/api/resources/object.js index 16f5db9c8d..9f9e7f2e32 100644 --- a/src/http/api/resources/object.js +++ b/src/http/api/resources/object.js @@ -520,7 +520,7 @@ exports.patchRmLink = { if (!request.query.arg[1]) { return reply({ - Message: 'cannot create link with no name!', + Message: 'cannot remove link with no name!', Code: 0 }).code(500).takeover() } @@ -545,11 +545,11 @@ exports.patchRmLink = { const link = request.pre.args.link const ipfs = request.server.app.ipfs - ipfs.object.patch.rmLink(root, link, (err, node) => { + ipfs.object.patch.rmLink(root, { name: link }, (err, node) => { if (err) { log.error(err) return reply({ - Message: 'Failed to add link to object: ' + err, + Message: 'Failed to remove link from object: ' + err, Code: 0 }).code(500) } diff --git a/test/core/pin-set.js b/test/core/pin-set.js index 33b50ad144..64b8fb1389 100644 --- a/test/core/pin-set.js +++ b/test/core/pin-set.js @@ -31,7 +31,7 @@ function createNodes (num, callback) { const items = [] for (let i = 0; i < num; i++) { items.push(cb => - createNode(String(i), (err, node) => cb(err, node._multihash)) + createNode(String(i), (err, node) => cb(err, node.multihash)) ) } @@ -73,7 +73,7 @@ describe('pinSet', function () { createNode('data', (err, node) => { expect(err).to.not.exist() - const nodeHash = node._multihash + const nodeHash = node.multihash pinSet.storeSet([nodeHash], (err, rootNode) => { expect(err).to.not.exist() const node = rootNode.toJSON() From 86c3d81e5cb47dfd1845f08443d3d322a5478a35 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 16 Aug 2018 18:12:38 +0100 Subject: [PATCH 34/51] fix: stub out call to fetch for ipfs.dns test in browser (#1512) Stubs self.fetch to return a static CID for calls to https://ipfs.io/api/v0/dns?arg=ipfs.io. Removes dependency on external service. License: MIT Signed-off-by: Alan Shaw --- test/core/interface.spec.js | 18 +++++++++++++++++- test/utils/dns-fetch-stub.js | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/utils/dns-fetch-stub.js diff --git a/test/core/interface.spec.js b/test/core/interface.spec.js index 67b9f974b3..44ccf730dd 100644 --- a/test/core/interface.spec.js +++ b/test/core/interface.spec.js @@ -1,11 +1,27 @@ -/* eslint-env mocha */ +/* eslint-env mocha, browser */ 'use strict' const tests = require('interface-ipfs-core') const CommonFactory = require('../utils/interface-common-factory') const isNode = require('detect-node') +const dnsFetchStub = require('../utils/dns-fetch-stub') describe('interface-ipfs-core tests', () => { + // ipfs.dns in the browser calls out to https://ipfs.io/api/v0/dns. + // The following code stubs self.fetch to return a static CID for calls + // to https://ipfs.io/api/v0/dns?arg=ipfs.io. + if (!isNode) { + const fetch = self.fetch + + before(() => { + self.fetch = dnsFetchStub(fetch) + }) + + after(() => { + self.fetch = fetch + }) + } + const defaultCommonFactory = CommonFactory.create() tests.bitswap(defaultCommonFactory, { skip: !isNode }) diff --git a/test/utils/dns-fetch-stub.js b/test/utils/dns-fetch-stub.js new file mode 100644 index 0000000000..a1e24a122c --- /dev/null +++ b/test/utils/dns-fetch-stub.js @@ -0,0 +1,16 @@ +'use strict' + +// Create a fetch stub with a fall through to the provided fetch implementation +// if the URL doesn't match https://ipfs.io/api/v0/dns?arg=ipfs.io. +module.exports = (fetch) => { + return function () { + if (arguments[0].startsWith('https://ipfs.io/api/v0/dns?arg=ipfs.io')) { + return Promise.resolve({ + json: () => Promise.resolve({ + Path: '/ipfs/QmYNQJoKGNHTpPxCBPh9KkDpaExgd2duMa3aF6ytMpHdao' + }) + }) + } + return fetch.apply(this, arguments) + } +} From 91a482b2d7ecbce3e882b969a47618978de7e8d0 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 17 Aug 2018 08:47:40 +0100 Subject: [PATCH 35/51] fix: improper input validation (#1506) * fix: callback with error for invalid CIDs * fix: handle invalid path for files.get* * fix: handling for invalid pin type * fix: specify async functions to use * fix: handle invalid bandwidth poll interval * refactor: allow libp2p to handle multiaddr validation * fix: differentiate between invalid multihash and invalid CID License: MIT Signed-off-by: Alan Shaw --- package.json | 1 + src/core/components/bitswap.js | 19 ++++++--- src/core/components/block.js | 20 +++++++-- src/core/components/dag.js | 30 +++++++++++-- src/core/components/dht.js | 15 ++++++- src/core/components/files.js | 40 ++++++++++------- src/core/components/object.js | 20 +++++++-- src/core/components/pin-set.js | 9 ++-- src/core/components/pin.js | 44 ++++++++++++------- src/core/components/stats.js | 5 ++- src/core/components/swarm.js | 9 ---- test/core/bitswap.spec.js | 10 +++++ test/core/block.spec.js | 69 ++++++++++++++++++++++++++++++ test/core/dag.spec.js | 67 +++++++++++++++++++++++++++++ test/core/dht.spec.js | 49 +++++++++++++++++++++ test/core/files.spec.js | 78 ++++++++++++++++++++++++++++++++++ test/core/object.spec.js | 59 +++++++++++++++++++++++++ test/core/pin.spec.js | 57 +++++++++++++++++++++++++ test/core/stats.spec.js | 53 +++++++++++++++++++++++ 19 files changed, 592 insertions(+), 62 deletions(-) create mode 100644 test/core/block.spec.js create mode 100644 test/core/dag.spec.js create mode 100644 test/core/dht.spec.js create mode 100644 test/core/files.spec.js create mode 100644 test/core/object.spec.js create mode 100644 test/core/pin.spec.js create mode 100644 test/core/stats.spec.js diff --git a/package.json b/package.json index 3cd1773ac3..d4510258a1 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "byteman": "^1.3.5", "cids": "~0.5.3", "debug": "^3.1.0", + "err-code": "^1.1.2", "file-type": "^8.1.0", "filesize": "^3.6.1", "fnv1a": "^1.0.1", diff --git a/src/core/components/bitswap.js b/src/core/components/bitswap.js index bd59eb3b16..652135c70a 100644 --- a/src/core/components/bitswap.js +++ b/src/core/components/bitswap.js @@ -6,6 +6,7 @@ const setImmediate = require('async/setImmediate') const Big = require('big.js') const CID = require('cids') const PeerId = require('peer-id') +const errCode = require('err-code') function formatWantlist (list) { return Array.from(list).map((e) => ({ '/': e[1].cid.toBaseEncodedString() })) @@ -69,12 +70,18 @@ module.exports = function bitswap (self) { if (!Array.isArray(keys)) { keys = [keys] } - keys = keys.map((key) => { - if (CID.isCID(key)) { - return key - } - return new CID(key) - }) + + try { + keys = keys.map((key) => { + if (CID.isCID(key)) { + return key + } + return new CID(key) + }) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } + return setImmediate(() => callback(null, self._bitswap.unwant(keys))) }) } diff --git a/src/core/components/block.js b/src/core/components/block.js index babb0e0752..f350363a01 100644 --- a/src/core/components/block.js +++ b/src/core/components/block.js @@ -5,7 +5,9 @@ const multihash = require('multihashes') const multihashing = require('multihashing-async') const CID = require('cids') const waterfall = require('async/waterfall') +const setImmediate = require('async/setImmediate') const promisify = require('promisify-es6') +const errCode = require('err-code') module.exports = function block (self) { return { @@ -17,7 +19,11 @@ module.exports = function block (self) { options = options || {} - cid = cleanCid(cid) + try { + cid = cleanCid(cid) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } if (options.preload !== false) { self._preload(cid) @@ -74,7 +80,11 @@ module.exports = function block (self) { ], callback) }), rm: promisify((cid, callback) => { - cid = cleanCid(cid) + try { + cid = cleanCid(cid) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } self._blockService.delete(cid, callback) }), stat: promisify((cid, options, callback) => { @@ -83,7 +93,11 @@ module.exports = function block (self) { options = {} } - cid = cleanCid(cid) + try { + cid = cleanCid(cid) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } if (options.preload !== false) { self._preload(cid) diff --git a/src/core/components/dag.js b/src/core/components/dag.js index c33539d97c..317d6acb8a 100644 --- a/src/core/components/dag.js +++ b/src/core/components/dag.js @@ -4,7 +4,9 @@ const promisify = require('promisify-es6') const CID = require('cids') const pull = require('pull-stream') const mapAsync = require('async/map') +const setImmediate = require('async/setImmediate') const flattenDeep = require('lodash/flattenDeep') +const errCode = require('err-code') module.exports = function dag (self) { return { @@ -52,7 +54,13 @@ module.exports = function dag (self) { if (typeof cid === 'string') { const split = cid.split('/') - cid = new CID(split[0]) + + try { + cid = new CID(split[0]) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } + split.shift() if (split.length > 0) { @@ -64,7 +72,7 @@ module.exports = function dag (self) { try { cid = new CID(cid) } catch (err) { - return callback(err) + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) } } @@ -96,7 +104,13 @@ module.exports = function dag (self) { if (typeof cid === 'string') { const split = cid.split('/') - cid = new CID(split[0]) + + try { + cid = new CID(split[0]) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } + split.shift() if (split.length > 0) { @@ -127,7 +141,15 @@ module.exports = function dag (self) { options = options || {} - self.dag.get(new CID(multihash), '', options, (err, res) => { + let cid + + try { + cid = new CID(multihash) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } + + self.dag.get(cid, '', options, (err, res) => { if (err) { return callback(err) } mapAsync(res.value.links, (link, cb) => { diff --git a/src/core/components/dht.js b/src/core/components/dht.js index e5edf281db..848b2e0621 100644 --- a/src/core/components/dht.js +++ b/src/core/components/dht.js @@ -5,7 +5,9 @@ const every = require('async/every') const PeerId = require('peer-id') const CID = require('cids') const each = require('async/each') +const setImmediate = require('async/setImmediate') // const bsplit = require('buffer-split') +const errCode = require('err-code') module.exports = (self) => { return { @@ -57,8 +59,19 @@ module.exports = (self) => { * @returns {Promise|void} */ findprovs: promisify((key, opts, callback) => { + if (typeof opts === 'function') { + callback = opts + opts = {} + } + + opts = opts || {} + if (typeof key === 'string') { - key = new CID(key) + try { + key = new CID(key) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } } if (typeof opts === 'function') { diff --git a/src/core/components/files.js b/src/core/components/files.js index 8877e19b77..de0b01c562 100644 --- a/src/core/components/files.js +++ b/src/core/components/files.js @@ -17,6 +17,7 @@ const Duplex = require('readable-stream').Duplex const OtherBuffer = require('buffer').Buffer const CID = require('cids') const toB58String = require('multihashes').toB58String +const errCode = require('err-code') const WRAPPER = 'wrapper/' @@ -348,7 +349,14 @@ module.exports = function files (self) { options = options || {} if (options.preload !== false) { - const pathComponents = normalizePath(ipfsPath).split('/') + let pathComponents + + try { + pathComponents = normalizePath(ipfsPath).split('/') + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_PATH'))) + } + self._preload(pathComponents[0]) } @@ -376,7 +384,14 @@ module.exports = function files (self) { options = options || {} if (options.preload !== false) { - const pathComponents = normalizePath(ipfsPath).split('/') + let pathComponents + + try { + pathComponents = normalizePath(ipfsPath).split('/') + } catch (err) { + return toStream.source(pull.error(errCode(err, 'ERR_INVALID_PATH'))) + } + self._preload(pathComponents[0]) } @@ -399,7 +414,14 @@ module.exports = function files (self) { options = options || {} if (options.preload !== false) { - const pathComponents = normalizePath(ipfsPath).split('/') + let pathComponents + + try { + pathComponents = normalizePath(ipfsPath).split('/') + } catch (err) { + return pull.error(errCode(err, 'ERR_INVALID_PATH')) + } + self._preload(pathComponents[0]) } @@ -414,11 +436,6 @@ module.exports = function files (self) { options = options || {} - if (options.preload !== false) { - const pathComponents = normalizePath(ipfsPath).split('/') - self._preload(pathComponents[0]) - } - pull( _lsPullStreamImmutable(ipfsPath, options), pull.collect((err, values) => { @@ -432,13 +449,6 @@ module.exports = function files (self) { }), lsReadableStreamImmutable: (ipfsPath, options) => { - options = options || {} - - if (options.preload !== false) { - const pathComponents = normalizePath(ipfsPath).split('/') - self._preload(pathComponents[0]) - } - return toStream.source(_lsPullStreamImmutable(ipfsPath, options)) }, diff --git a/src/core/components/object.js b/src/core/components/object.js index 3b6012ed35..d83deae62a 100644 --- a/src/core/components/object.js +++ b/src/core/components/object.js @@ -9,6 +9,7 @@ const DAGLink = dagPB.DAGLink const CID = require('cids') const mh = require('multihashes') const Unixfs = require('ipfs-unixfs') +const errCode = require('err-code') function normalizeMultihash (multihash, enc) { if (typeof multihash === 'string') { @@ -188,7 +189,13 @@ module.exports = function object (self) { } function next () { - const cid = new CID(node.multihash) + let cid + + try { + cid = new CID(node.multihash) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) + } self._ipld.put(node, { cid }, (err) => { if (err) { @@ -210,14 +217,19 @@ module.exports = function object (self) { options = {} } - let mh + let mh, cid try { mh = normalizeMultihash(multihash, options.enc) } catch (err) { - return callback(err) + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_MULTIHASH'))) + } + + try { + cid = new CID(mh) + } catch (err) { + return setImmediate(() => callback(errCode(err, 'ERR_INVALID_CID'))) } - let cid = new CID(mh) if (options.cidVersion === 1) { cid = cid.toV1() diff --git a/src/core/components/pin-set.js b/src/core/components/pin-set.js index d100b85311..1f31697825 100644 --- a/src/core/components/pin-set.js +++ b/src/core/components/pin-set.js @@ -6,7 +6,8 @@ const protobuf = require('protons') const fnv1a = require('fnv1a') const varint = require('varint') const { DAGNode, DAGLink } = require('ipld-dag-pb') -const async = require('async') +const some = require('async/some') +const eachOf = require('async/eachOf') const pbSchema = require('./pin.proto') @@ -67,7 +68,7 @@ exports = module.exports = function (dag) { return searchChildren(root, callback) function searchChildren (root, cb) { - async.some(root.links, ({ multihash }, someCb) => { + some(root.links, ({ multihash }, someCb) => { const bs58Link = toB58String(multihash) if (bs58Link === childhash) { return someCb(null, true) } if (bs58Link in seen) { return someCb(null, false) } @@ -150,7 +151,7 @@ exports = module.exports = function (dag) { return bins }, {}) - async.eachOf(bins, (bin, idx, eachCb) => { + eachOf(bins, (bin, idx, eachCb) => { storePins( bin, depth + 1, @@ -203,7 +204,7 @@ exports = module.exports = function (dag) { return callback(err) } - async.eachOf(node.links, (link, idx, eachCb) => { + eachOf(node.links, (link, idx, eachCb) => { if (idx < pbh.header.fanout) { // the first pbh.header.fanout links are fanout bins // if a fanout bin is not 'empty', dig into and walk its DAGLinks diff --git a/src/core/components/pin.js b/src/core/components/pin.js index d1d7456fb6..196ca1a67b 100644 --- a/src/core/components/pin.js +++ b/src/core/components/pin.js @@ -4,8 +4,16 @@ const promisify = require('promisify-es6') const { DAGNode, DAGLink } = require('ipld-dag-pb') const CID = require('cids') -const async = require('async') +const map = require('async/map') +const mapSeries = require('async/mapSeries') +const series = require('async/series') +const parallel = require('async/parallel') +const eachLimit = require('async/eachLimit') +const waterfall = require('async/waterfall') +const someLimit = require('async/someLimit') +const setImmediate = require('async/setImmediate') const { Key } = require('interface-datastore') +const errCode = require('err-code') const createPinSet = require('./pin-set') const { resolvePath } = require('../utils') @@ -18,6 +26,11 @@ function toB58String (hash) { return new CID(hash).toBaseEncodedString() } +function invalidPinTypeErr (type) { + const errMsg = `Invalid type '${type}', must be one of {direct, indirect, recursive, all}` + return errCode(new Error(errMsg), 'ERR_INVALID_PIN_TYPE') +} + module.exports = (self) => { const repo = self._repo const dag = self.dag @@ -39,7 +52,7 @@ module.exports = (self) => { function getIndirectKeys (callback) { const indirectKeys = new Set() - async.eachLimit(recursiveKeys(), concurrencyLimit, (multihash, cb) => { + eachLimit(recursiveKeys(), concurrencyLimit, (multihash, cb) => { dag._getRecursive(multihash, (err, nodes) => { if (err) { return cb(err) } @@ -62,16 +75,16 @@ module.exports = (self) => { // a DAGNode holding those as DAGLinks, a kind of root pin function flushPins (callback) { let dLink, rLink, root - async.series([ + series([ // create a DAGLink to the node with direct pins - cb => async.waterfall([ + cb => waterfall([ cb => pinset.storeSet(directKeys(), cb), (node, cb) => DAGLink.create(types.direct, node.size, node.multihash, cb), (link, cb) => { dLink = link; cb(null) } ], cb), // create a DAGLink to the node with recursive pins - cb => async.waterfall([ + cb => waterfall([ cb => pinset.storeSet(recursiveKeys(), cb), (node, cb) => DAGLink.create(types.recursive, node.size, node.multihash, cb), (link, cb) => { rLink = link; cb(null) } @@ -114,7 +127,7 @@ module.exports = (self) => { if (err) { return callback(err) } // verify that each hash can be pinned - async.map(mhs, (multihash, cb) => { + map(mhs, (multihash, cb) => { const key = toB58String(multihash) if (recursive) { if (recursivePins.has(key)) { @@ -174,7 +187,7 @@ module.exports = (self) => { if (err) { return callback(err) } // verify that each hash can be unpinned - async.map(mhs, (multihash, cb) => { + map(mhs, (multihash, cb) => { pin._isPinnedWithType(multihash, types.all, (err, res) => { if (err) { return cb(err) } const { pinned, reason } = res @@ -235,12 +248,13 @@ module.exports = (self) => { paths = null } if (options && options.type) { + if (typeof options.type !== 'string') { + return setImmediate(() => callback(invalidPinTypeErr(options.type))) + } type = options.type.toLowerCase() } - if (!types[type]) { - return callback(new Error( - `Invalid type '${type}', must be one of {direct, indirect, recursive, all}` - )) + if (!Object.keys(types).includes(type)) { + return setImmediate(() => callback(invalidPinTypeErr(type))) } if (paths) { @@ -248,7 +262,7 @@ module.exports = (self) => { resolvePath(self.object, paths, (err, mhs) => { if (err) { return callback(err) } - async.mapSeries(mhs, (multihash, cb) => { + mapSeries(mhs, (multihash, cb) => { pin._isPinnedWithType(multihash, types.all, (err, res) => { if (err) { return cb(err) } const { pinned, reason } = res @@ -336,7 +350,7 @@ module.exports = (self) => { // check each recursive key to see if multihash is under it // arbitrary limit, enables handling 1000s of pins. let foundPin - async.someLimit(recursiveKeys(), concurrencyLimit, (key, cb) => { + someLimit(recursiveKeys(), concurrencyLimit, (key, cb) => { dag.get(new CID(key), (err, res) => { if (err) { return cb(err) } @@ -354,7 +368,7 @@ module.exports = (self) => { }), _load: promisify(callback => { - async.waterfall([ + waterfall([ // hack for CLI tests (cb) => repo.closed ? repo.datastore.open(cb) : cb(null, null), (_, cb) => repo.datastore.has(pinDataStoreKey, cb), @@ -371,7 +385,7 @@ module.exports = (self) => { } } - async.parallel([ + parallel([ cb => pinset.loadSet(pinRoot.value, types.recursive, cb), cb => pinset.loadSet(pinRoot.value, types.direct, cb) ], (err, keys) => { diff --git a/src/core/components/stats.js b/src/core/components/stats.js index ad87cf981e..010a01431d 100644 --- a/src/core/components/stats.js +++ b/src/core/components/stats.js @@ -5,6 +5,7 @@ const Big = require('big.js') const Pushable = require('pull-pushable') const human = require('human-to-milliseconds') const toStream = require('pull-stream-to-stream') +const errCode = require('err-code') function bandwidthStats (self, opts) { return new Promise((resolve, reject) => { @@ -49,7 +50,9 @@ module.exports = function stats (self) { if (opts.poll) { human(opts.interval || '1s', (err, value) => { - if (err) throw err + if (err) { + return stream.end(errCode(err, 'ERR_INVALID_POLL_INTERVAL')) + } interval = setInterval(() => { bandwidthStats(self, opts) diff --git a/src/core/components/swarm.js b/src/core/components/swarm.js index 5d1c8d26b6..c6c4d3073c 100644 --- a/src/core/components/swarm.js +++ b/src/core/components/swarm.js @@ -1,6 +1,5 @@ 'use strict' -const multiaddr = require('multiaddr') const promisify = require('promisify-es6') const values = require('lodash/values') @@ -67,10 +66,6 @@ module.exports = function swarm (self) { return callback(new Error(OFFLINE_ERROR)) } - if (typeof maddr === 'string') { - maddr = multiaddr(maddr) - } - self._libp2pNode.dial(maddr, callback) }), @@ -79,10 +74,6 @@ module.exports = function swarm (self) { return callback(new Error(OFFLINE_ERROR)) } - if (typeof maddr === 'string') { - maddr = multiaddr(maddr) - } - self._libp2pNode.hangUp(maddr, callback) }), diff --git a/test/core/bitswap.spec.js b/test/core/bitswap.spec.js index ebadb2b99b..5c0ce9f976 100644 --- a/test/core/bitswap.spec.js +++ b/test/core/bitswap.spec.js @@ -239,4 +239,14 @@ describe('bitswap', function () { }) }) }) + + describe('unwant', () => { + it('should callback with error for invalid CID input', (done) => { + inProcNode.bitswap.unwant('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) }) diff --git a/test/core/block.spec.js b/test/core/block.spec.js new file mode 100644 index 0000000000..fc47d3bf39 --- /dev/null +++ b/test/core/block.spec.js @@ -0,0 +1,69 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('block', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('get', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.block.get('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) + + describe('rm', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.block.rm('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) + + describe('stat', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.block.stat('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) +}) diff --git a/test/core/dag.spec.js b/test/core/dag.spec.js new file mode 100644 index 0000000000..1c71d79943 --- /dev/null +++ b/test/core/dag.spec.js @@ -0,0 +1,67 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('dag', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('get', () => { + it('should callback with error for invalid string CID input', (done) => { + ipfs.dag.get('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + + it('should callback with error for invalid buffer CID input', (done) => { + ipfs.dag.get(Buffer.from('INVALID CID'), (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) + + describe('tree', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.dag.tree('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) +}) diff --git a/test/core/dht.spec.js b/test/core/dht.spec.js new file mode 100644 index 0000000000..0f08b15bb1 --- /dev/null +++ b/test/core/dht.spec.js @@ -0,0 +1,49 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('dht', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('findprovs', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.dht.findprovs('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) +}) diff --git a/test/core/files.spec.js b/test/core/files.spec.js new file mode 100644 index 0000000000..e34ecbed2c --- /dev/null +++ b/test/core/files.spec.js @@ -0,0 +1,78 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const pull = require('pull-stream') +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('files', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('get', () => { + it('should callback with error for invalid IPFS path input', (done) => { + const invalidPath = null + ipfs.files.get(invalidPath, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_PATH') + done() + }) + }) + }) + + describe('getReadableStream', () => { + it('should return erroring stream for invalid IPFS path input', (done) => { + const invalidPath = null + const stream = ipfs.files.getReadableStream(invalidPath) + + stream.on('error', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_PATH') + done() + }) + }) + }) + + describe('getPullStream', () => { + it('should return erroring stream for invalid IPFS path input', (done) => { + const invalidPath = null + pull( + ipfs.files.getPullStream(invalidPath), + pull.collect((err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_PATH') + done() + }) + ) + }) + }) +}) diff --git a/test/core/object.spec.js b/test/core/object.spec.js new file mode 100644 index 0000000000..d126bb94b6 --- /dev/null +++ b/test/core/object.spec.js @@ -0,0 +1,59 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('object', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('get', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.object.get('INVALID CID', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) + + describe('put', () => { + it('should callback with error for invalid CID input', (done) => { + ipfs.object.put({ multihash: 'INVALID CID' }, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_CID') + done() + }) + }) + }) +}) diff --git a/test/core/pin.spec.js b/test/core/pin.spec.js new file mode 100644 index 0000000000..3b530db147 --- /dev/null +++ b/test/core/pin.spec.js @@ -0,0 +1,57 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('pin', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('ls', () => { + it('should callback with error for invalid non-string pin type option', (done) => { + ipfs.pin.ls({ type: 6 }, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_PIN_TYPE') + done() + }) + }) + + it('should callback with error for invalid string pin type option', (done) => { + ipfs.pin.ls({ type: '__proto__' }, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_PIN_TYPE') + done() + }) + }) + }) +}) diff --git a/test/core/stats.spec.js b/test/core/stats.spec.js new file mode 100644 index 0000000000..15e872efaf --- /dev/null +++ b/test/core/stats.spec.js @@ -0,0 +1,53 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const pull = require('pull-stream') +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('stats', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + describe('bwPullStream', () => { + it('should return erroring stream for invalid interval option', (done) => { + pull( + ipfs.stats.bwPullStream({ poll: true, interval: 'INVALID INTERVAL' }), + pull.collect((err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_POLL_INTERVAL') + done() + }) + ) + }) + }) +}) From fc230f1b74a7cb5c8ec6d00b418e27755225f602 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 17 Aug 2018 08:52:01 +0100 Subject: [PATCH 36/51] chore: update contributors From 04d8ce30ebfcd8093286a4eed97cdcb3cc3fe4c9 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 17 Aug 2018 08:52:02 +0100 Subject: [PATCH 37/51] chore: release version v0.31.5 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a2ad0e43..56605f8f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ + +## [0.31.5](https://github.com/ipfs/js-ipfs/compare/v0.31.4...v0.31.5) (2018-08-17) + + +### Bug Fixes + +* add missing space after emoji ([5cde7c1](https://github.com/ipfs/js-ipfs/commit/5cde7c1)) +* improper input validation ([#1506](https://github.com/ipfs/js-ipfs/issues/1506)) ([91a482b](https://github.com/ipfs/js-ipfs/commit/91a482b)) +* object.patch.rmLink not working ([#1508](https://github.com/ipfs/js-ipfs/issues/1508)) ([afd3255](https://github.com/ipfs/js-ipfs/commit/afd3255)) +* stub out call to fetch for ipfs.dns test in browser ([#1512](https://github.com/ipfs/js-ipfs/issues/1512)) ([86c3d81](https://github.com/ipfs/js-ipfs/commit/86c3d81)) + + + ## [0.31.4](https://github.com/ipfs/js-ipfs/compare/v0.31.3...v0.31.4) (2018-08-09) diff --git a/package.json b/package.json index d4510258a1..4292285807 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.4", + "version": "0.31.5", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From 1eb84855326dd9dfecceca58d72b432aaef9fc72 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Fri, 17 Aug 2018 09:42:26 +0100 Subject: [PATCH 38/51] feat: adds data-encoding argument to control data encoding (#1420) * feat: adds data-encoding argument to control data encoding Allows the user to specify how object data is returned to prevent default byte encoding from emitting characters that are not valid in JSON. * fix: increase test timeouts as they are slow * chore: update deps --- package.json | 4 ++-- src/cli/commands/object/get.js | 11 +++++++++-- src/http/api/resources/object.js | 4 +++- test/cli/object.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4292285807..99cbecd4a4 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "expose-loader": "~0.7.5", "form-data": "^2.3.2", "hat": "0.0.3", - "interface-ipfs-core": "~0.75.2", + "interface-ipfs-core": "~0.76.1", "ipfsd-ctl": "~0.39.1", "mocha": "^5.2.0", "ncp": "^2.0.0", @@ -105,7 +105,7 @@ "hoek": "^5.0.3", "human-to-milliseconds": "^1.0.0", "interface-datastore": "~0.4.2", - "ipfs-api": "^22.2.4", + "ipfs-api": "^24.0.0", "ipfs-bitswap": "~0.20.3", "ipfs-block": "~0.7.1", "ipfs-block-service": "~0.14.0", diff --git a/src/cli/commands/object/get.js b/src/cli/commands/object/get.js index 629a3a82d1..9bb9e71542 100644 --- a/src/cli/commands/object/get.js +++ b/src/cli/commands/object/get.js @@ -7,7 +7,12 @@ module.exports = { describe: 'Get and serialize the DAG node named by ', - builder: {}, + builder: { + 'data-encoding': { + type: 'string', + default: 'base64' + } + }, handler (argv) { argv.ipfs.object.get(argv.key, {enc: 'base58'}, (err, node) => { @@ -16,7 +21,9 @@ module.exports = { } const nodeJSON = node.toJSON() - nodeJSON.data = nodeJSON.data ? nodeJSON.data.toString() : '' + if (Buffer.isBuffer(node.data)) { + nodeJSON.data = node.data.toString(argv['data-encoding'] || undefined) + } const answer = { Data: nodeJSON.data, diff --git a/src/http/api/resources/object.js b/src/http/api/resources/object.js index 9f9e7f2e32..dc68c71251 100644 --- a/src/http/api/resources/object.js +++ b/src/http/api/resources/object.js @@ -85,7 +85,9 @@ exports.get = { const nodeJSON = node.toJSON() - nodeJSON.data = nodeJSON.data ? nodeJSON.data.toString() : '' + if (Buffer.isBuffer(node.data)) { + nodeJSON.data = node.data.toString(request.query['data-encoding'] || undefined) + } const answer = { Data: nodeJSON.data, diff --git a/test/cli/object.js b/test/cli/object.js index 4173ecd816..1462d00d55 100644 --- a/test/cli/object.js +++ b/test/cli/object.js @@ -41,6 +41,34 @@ describe('object', () => runOnAndOff((thing) => { }) }) + it('get with data', function () { + this.timeout(15 * 1000) + + return ipfs('object new') + .then((out) => out.trim()) + .then((hash) => ipfs(`object patch set-data ${hash} test/fixtures/test-data/hello`)) + .then((out) => out.trim()) + .then((hash) => ipfs(`object get ${hash}`)) + .then((out) => { + const result = JSON.parse(out) + expect(result.Data).to.eql('aGVsbG8gd29ybGQK') + }) + }) + + it('get while overriding data-encoding', function () { + this.timeout(15 * 1000) + + return ipfs('object new') + .then((out) => out.trim()) + .then((hash) => ipfs(`object patch set-data ${hash} test/fixtures/test-data/hello`)) + .then((out) => out.trim()) + .then((hash) => ipfs(`object get --data-encoding=utf8 ${hash}`)) + .then((out) => { + const result = JSON.parse(out) + expect(result.Data).to.eql('hello world\n') + }) + }) + it('put', () => { return ipfs('object put test/fixtures/test-data/node.json').then((out) => { expect(out).to.eql( From bb030e305b99129201764c65ef01fbec0b2fe585 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 17 Aug 2018 13:42:17 +0100 Subject: [PATCH 39/51] chore: update contributors From 9368f37bd9913743ea2166d3689cfbe4bf8c9421 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 17 Aug 2018 13:42:18 +0100 Subject: [PATCH 40/51] chore: release version v0.31.6 --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56605f8f52..181c220c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [0.31.6](https://github.com/ipfs/js-ipfs/compare/v0.31.5...v0.31.6) (2018-08-17) + + +### Features + +* adds data-encoding argument to control data encoding ([#1420](https://github.com/ipfs/js-ipfs/issues/1420)) ([1eb8485](https://github.com/ipfs/js-ipfs/commit/1eb8485)) + + + ## [0.31.5](https://github.com/ipfs/js-ipfs/compare/v0.31.4...v0.31.5) (2018-08-17) diff --git a/package.json b/package.json index 99cbecd4a4..417c7a56f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.5", + "version": "0.31.6", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From 511ab47d93f2c0b7f4160e623268ce4a1d769f6b Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 20 Aug 2018 17:23:57 +0100 Subject: [PATCH 41/51] fix: fails to start when preload disabled (#1516) fixes #1514 License: MIT Signed-off-by: Alan Shaw --- src/core/preload.js | 5 ++++- test/core/preload.spec.js | 36 ++++++++++++++++++++++++++++++++- test/utils/mock-preload-node.js | 5 +++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/core/preload.js b/src/core/preload.js index d99a9d8f20..2902005fc2 100644 --- a/src/core/preload.js +++ b/src/core/preload.js @@ -18,11 +18,14 @@ module.exports = self => { options.addresses = options.addresses || [] if (!options.enabled || !options.addresses.length) { - return (_, callback) => { + const api = (_, callback) => { if (callback) { setImmediate(() => callback()) } } + api.start = () => {} + api.stop = () => {} + return api } let stopped = true diff --git a/test/core/preload.spec.js b/test/core/preload.spec.js index dc8527c552..0d105f2837 100644 --- a/test/core/preload.spec.js +++ b/test/core/preload.spec.js @@ -2,6 +2,8 @@ /* eslint-env mocha */ 'use strict' +const path = require('path') +const os = require('os') const hat = require('hat') const CID = require('cids') const parallel = require('async/parallel') @@ -17,8 +19,11 @@ const IPFS = require('../../src') describe('preload', () => { let ipfs - before((done) => { + before(function (done) { + this.timeout(10 * 1000) + ipfs = new IPFS({ + repo: path.join(os.tmpdir(), hat()), config: { Addresses: { Swarm: [] @@ -286,4 +291,33 @@ describe('preload', () => { }) }) }) + + it('should not preload if disabled', function (done) { + this.timeout(10 * 1000) + + const ipfs = new IPFS({ + repo: path.join(os.tmpdir(), hat()), + config: { + Addresses: { + Swarm: [] + } + }, + preload: { + enabled: false, + addresses: [MockPreloadNode.defaultAddr] + } + }) + + ipfs.on('ready', () => { + ipfs.files.add(Buffer.from(hat()), (err, res) => { + expect(err).to.not.exist() + + MockPreloadNode.waitForCids(res[0].hash, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_TIMEOUT') + done() + }) + }) + }) + }) }) diff --git a/test/utils/mock-preload-node.js b/test/utils/mock-preload-node.js index 370847bf72..795413a31d 100644 --- a/test/utils/mock-preload-node.js +++ b/test/utils/mock-preload-node.js @@ -4,6 +4,7 @@ const http = require('http') const toUri = require('multiaddr-to-uri') const URL = require('url').URL || self.URL +const errCode = require('err-code') const defaultPort = 1138 const defaultAddr = `/dnsaddr/localhost/tcp/${defaultPort}` @@ -146,10 +147,10 @@ module.exports.waitForCids = (cids, opts, cb) => { } if (Date.now() > start + opts.timeout) { - return cb(new Error('Timed out waiting for CIDs to be preloaded')) + return cb(errCode(new Error('Timed out waiting for CIDs to be preloaded'), 'ERR_TIMEOUT')) } - setTimeout(checkForCid, 10) + setTimeout(checkForCid, 5) }) } From 4a68ac10f65f9281d53c774bf6234ae20dd2a2ce Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 20 Aug 2018 17:25:21 +0100 Subject: [PATCH 42/51] fix: npm publishes examples folder (#1513) License: MIT Signed-off-by: Alan Shaw --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index 59335fda64..6609102526 100644 --- a/.npmignore +++ b/.npmignore @@ -32,3 +32,4 @@ build node_modules test +examples From c4decd7187aa7723e1dfc566b37792eb17f81c88 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 20 Aug 2018 21:20:33 +0100 Subject: [PATCH 43/51] chore: update contributors From 3d2a8745949d17ae9ce64e89cc231c1d526dd4b4 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Mon, 20 Aug 2018 21:20:34 +0100 Subject: [PATCH 44/51] chore: release version v0.31.7 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181c220c04..1a516395d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +## [0.31.7](https://github.com/ipfs/js-ipfs/compare/v0.31.6...v0.31.7) (2018-08-20) + + +### Bug Fixes + +* fails to start when preload disabled ([#1516](https://github.com/ipfs/js-ipfs/issues/1516)) ([511ab47](https://github.com/ipfs/js-ipfs/commit/511ab47)), closes [#1514](https://github.com/ipfs/js-ipfs/issues/1514) +* npm publishes examples folder ([#1513](https://github.com/ipfs/js-ipfs/issues/1513)) ([4a68ac1](https://github.com/ipfs/js-ipfs/commit/4a68ac1)) + + + ## [0.31.6](https://github.com/ipfs/js-ipfs/compare/v0.31.5...v0.31.6) (2018-08-17) diff --git a/package.json b/package.json index 417c7a56f4..60d3d8de17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfs", - "version": "0.31.6", + "version": "0.31.7", "description": "JavaScript implementation of the IPFS specification", "leadMaintainer": "Alan Shaw ", "bin": { From 4df45d1830e5e67915db941daa7ce906063a3647 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 22 Aug 2018 08:05:32 +0100 Subject: [PATCH 45/51] feat: add partial implementation for ipfs.resolve (#1455) * feat: add partial implementation for ipfs.resolve License: MIT Signed-off-by: Alan Shaw * feat: WIP add ipfs.resolve CLI and HTTP command License: MIT Signed-off-by: Alan Shaw * test: add resolve IPFS path test License: MIT Signed-off-by: Alan Shaw * fix: command count for tests License: MIT Signed-off-by: Alan Shaw * fix: skip tests that do not pass on js-ipfs yet License: MIT Signed-off-by: Alan Shaw * fix: increase timeout for resolve tests License: MIT Signed-off-by: Alan Shaw * fix: remove .only License: MIT Signed-off-by: Alan Shaw * chore: update ipfs-api dependency License: MIT Signed-off-by: Alan Shaw * chore: update interface-ipfs-core dependency License: MIT Signed-off-by: Alan Shaw --- src/cli/commands/resolve.js | 24 +++++++++ src/core/components/index.js | 1 + src/core/components/resolve.js | 86 +++++++++++++++++++++++++++++++ src/core/index.js | 1 + src/http/api/resources/index.js | 1 + src/http/api/resources/resolve.js | 37 +++++++++++++ src/http/api/routes/index.js | 1 + src/http/api/routes/resolve.js | 16 ++++++ test/cli/commands.js | 2 +- test/cli/resolve.js | 56 ++++++++++++++++++++ test/core/interface.spec.js | 8 ++- test/http-api/interface.js | 8 ++- 12 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 src/cli/commands/resolve.js create mode 100644 src/core/components/resolve.js create mode 100644 src/http/api/resources/resolve.js create mode 100644 src/http/api/routes/resolve.js create mode 100644 test/cli/resolve.js diff --git a/src/cli/commands/resolve.js b/src/cli/commands/resolve.js new file mode 100644 index 0000000000..c1b7a54991 --- /dev/null +++ b/src/cli/commands/resolve.js @@ -0,0 +1,24 @@ +'use strict' + +const print = require('../utils').print + +module.exports = { + command: 'resolve ', + + description: 'Resolve the value of names to IPFS', + + builder: { + recursive: { + alias: 'r', + type: 'boolean', + default: false + } + }, + + handler (argv) { + argv.ipfs.resolve(argv.name, { recursive: argv.recursive }, (err, res) => { + if (err) throw err + print(res) + }) + } +} diff --git a/src/core/components/index.js b/src/core/components/index.js index 1f6f084dee..6fc833499d 100644 --- a/src/core/components/index.js +++ b/src/core/components/index.js @@ -27,3 +27,4 @@ exports.dns = require('./dns') exports.key = require('./key') exports.stats = require('./stats') exports.mfs = require('./mfs') +exports.resolve = require('./resolve') diff --git a/src/core/components/resolve.js b/src/core/components/resolve.js new file mode 100644 index 0000000000..dd866cbfbb --- /dev/null +++ b/src/core/components/resolve.js @@ -0,0 +1,86 @@ +'use strict' + +const promisify = require('promisify-es6') +const isIpfs = require('is-ipfs') +const setImmediate = require('async/setImmediate') +const doUntil = require('async/doUntil') +const CID = require('cids') + +module.exports = (self) => { + return promisify((name, opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = opts || {} + + if (!isIpfs.path(name)) { + return setImmediate(() => cb(new Error('invalid argument'))) + } + + // TODO remove this and update subsequent code when IPNS is implemented + if (!isIpfs.ipfsPath(name)) { + return setImmediate(() => cb(new Error('resolve non-IPFS names is not implemented'))) + } + + const split = name.split('/') // ['', 'ipfs', 'hash', ...path] + const cid = new CID(split[2]) + + if (split.length === 3) { + return setImmediate(() => cb(null, name)) + } + + const path = split.slice(3).join('/') + + resolve(cid, path, (err, cid) => { + if (err) return cb(err) + if (!cid) return cb(new Error('found non-link at given path')) + cb(null, `/ipfs/${cid.toBaseEncodedString(opts.cidBase)}`) + }) + }) + + // Resolve the given CID + path to a CID. + function resolve (cid, path, callback) { + let value + + doUntil( + (cb) => { + self.block.get(cid, (err, block) => { + if (err) return cb(err) + + const r = self._ipld.resolvers[cid.codec] + + if (!r) { + return cb(new Error(`No resolver found for codec "${cid.codec}"`)) + } + + r.resolver.resolve(block.data, path, (err, result) => { + if (err) return cb(err) + value = result.value + path = result.remainderPath + cb() + }) + }) + }, + () => { + const endReached = !path || path === '/' + + if (endReached) { + return true + } + + if (value) { + cid = new CID(value['/']) + } + + return false + }, + (err) => { + if (err) return callback(err) + if (value && value['/']) return callback(null, new CID(value['/'])) + callback() + } + ) + } +} diff --git a/src/core/index.js b/src/core/index.js index 52266b7b41..f9b29c5145 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -120,6 +120,7 @@ class IPFS extends EventEmitter { this.dns = components.dns(this) this.key = components.key(this) this.stats = components.stats(this) + this.resolve = components.resolve(this) if (this._options.EXPERIMENTAL.pubsub) { this.log('EXPERIMENTAL pubsub is enabled') diff --git a/src/http/api/resources/index.js b/src/http/api/resources/index.js index 59040a99d8..f937bf2e1b 100644 --- a/src/http/api/resources/index.js +++ b/src/http/api/resources/index.js @@ -18,3 +18,4 @@ exports.pubsub = require('./pubsub') exports.dns = require('./dns') exports.key = require('./key') exports.stats = require('./stats') +exports.resolve = require('./resolve') diff --git a/src/http/api/resources/resolve.js b/src/http/api/resources/resolve.js new file mode 100644 index 0000000000..417f50d8e8 --- /dev/null +++ b/src/http/api/resources/resolve.js @@ -0,0 +1,37 @@ +'use strict' + +const Joi = require('joi') +const debug = require('debug') + +const log = debug('jsipfs:http-api:resolve') +log.error = debug('jsipfs:http-api:resolve:error') + +module.exports = { + validate: { + query: Joi.object().keys({ + r: Joi.alternatives() + .when('recursive', { + is: Joi.any().exist(), + then: Joi.any().forbidden(), + otherwise: Joi.boolean() + }), + recursive: Joi.boolean(), + arg: Joi.string().required() + }).unknown() + }, + handler (request, reply) { + const ipfs = request.server.app.ipfs + const name = request.query.arg + const recursive = request.query.r || request.query.recursive || false + + log(name, { recursive }) + + ipfs.resolve(name, { recursive }, (err, res) => { + if (err) { + log.error(err) + return reply({ Message: err.message, Code: 0 }).code(500) + } + reply({ Path: res }) + }) + } +} diff --git a/src/http/api/routes/index.js b/src/http/api/routes/index.js index bfec26a460..4087299ecd 100644 --- a/src/http/api/routes/index.js +++ b/src/http/api/routes/index.js @@ -21,4 +21,5 @@ module.exports = (server) => { require('./dns')(server) require('./key')(server) require('./stats')(server) + require('./resolve')(server) } diff --git a/src/http/api/routes/resolve.js b/src/http/api/routes/resolve.js new file mode 100644 index 0000000000..259ae3bdd1 --- /dev/null +++ b/src/http/api/routes/resolve.js @@ -0,0 +1,16 @@ +'use strict' + +const resources = require('./../resources') + +module.exports = (server) => { + const api = server.select('API') + + api.route({ + method: '*', + path: '/api/v0/resolve', + config: { + handler: resources.resolve.handler, + validate: resources.resolve.validate + } + }) +} diff --git a/test/cli/commands.js b/test/cli/commands.js index 2c0c5fc033..2c52e23d74 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 = 77 +const commandCount = 78 describe('commands', () => runOnAndOff((thing) => { let ipfs diff --git a/test/cli/resolve.js b/test/cli/resolve.js new file mode 100644 index 0000000000..fe670d4d15 --- /dev/null +++ b/test/cli/resolve.js @@ -0,0 +1,56 @@ +/* eslint-env mocha */ +'use strict' + +const path = require('path') +const expect = require('chai').expect +const isIpfs = require('is-ipfs') + +const runOnAndOff = require('../utils/on-and-off') + +describe('resolve', () => runOnAndOff((thing) => { + let ipfs + + before(() => { + ipfs = thing.ipfs + }) + + it('should resolve an IPFS hash', function () { + this.timeout(10 * 1000) + + const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme') + let hash + + return ipfs(`add ${filePath}`) + .then((out) => { + hash = out.split(' ')[1] + expect(isIpfs.cid(hash)).to.be.true() + return ipfs(`resolve /ipfs/${hash}`) + }) + .then((out) => { + expect(out).to.contain(`/ipfs/${hash}`) + }) + }) + + it('should resolve an IPFS path link', function () { + this.timeout(10 * 1000) + + const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme') + let fileHash, rootHash + + return ipfs(`add ${filePath} --wrap-with-directory`) + .then((out) => { + const lines = out.split('\n') + + fileHash = lines[0].split(' ')[1] + rootHash = lines[1].split(' ')[1] + + expect(isIpfs.cid(fileHash)).to.be.true() + expect(isIpfs.cid(rootHash)).to.be.true() + + return ipfs(`resolve /ipfs/${rootHash}/readme`) + }) + .then((out) => { + expect(out).to.contain(`/ipfs/${fileHash}`) + }) + }) +})) diff --git a/test/core/interface.spec.js b/test/core/interface.spec.js index 44ccf730dd..8ac1655b97 100644 --- a/test/core/interface.spec.js +++ b/test/core/interface.spec.js @@ -55,8 +55,12 @@ describe('interface-ipfs-core tests', () => { }), { skip: [ { - name: 'resolve', - reason: 'TODO: not implemented' + name: 'should resolve an IPNS DNS link', + reason: 'TODO IPNS not implemented yet' + }, + { + name: 'should resolve IPNS link recursively', + reason: 'TODO IPNS not implemented yet' } ] }) diff --git a/test/http-api/interface.js b/test/http-api/interface.js index b27c2b22fb..ac33dfb370 100644 --- a/test/http-api/interface.js +++ b/test/http-api/interface.js @@ -40,8 +40,12 @@ describe('interface-ipfs-core over ipfs-api tests', () => { }), { skip: [ { - name: 'resolve', - reason: 'TODO: not implemented' + name: 'should resolve an IPNS DNS link', + reason: 'TODO IPNS not implemented yet' + }, + { + name: 'should resolve IPNS link recursively', + reason: 'TODO IPNS not implemented yet' } ] }) From 4561f655d5b672baa86f1e918b93787854874644 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 22 Aug 2018 11:43:19 +0200 Subject: [PATCH 46/51] feat: use package-table vs custom script --- README.md | 9 +- package-list.json | 58 ++++++++++ scripts/generate-package-table-for-readme.js | 115 ------------------- 3 files changed, 61 insertions(+), 121 deletions(-) create mode 100644 package-list.json delete mode 100755 scripts/generate-package-table-for-readme.js diff --git a/README.md b/README.md index 2a363398cb..7c3ea29a76 100644 --- a/README.md +++ b/README.md @@ -757,17 +757,14 @@ $ curl --silent localhost:5002/api/v0/id | jq .ID ## Packages -Listing of the main packages used in the IPFS ecosystem. There are also three -specifications worth linking here: +Listing of the main packages used in the IPFS ecosystem. There are also three specifications worth linking here: - [`interface-ipfs-core`](https://github.com/ipfs/interface-ipfs-core) - [`http-api-spec`](https://github.com/ipfs/http-api-spec) - [`cli spec`](https://github.com/ipfs/specs/tree/master/public-api/cli) - +> This table is generated using the module `package-table` with `package-table --data=package-list.json`. + | Package | Version | Deps | CI | Coverage | | ---------|---------|---------|---------|--------- | | **Files** | diff --git a/package-list.json b/package-list.json new file mode 100644 index 0000000000..7259091d66 --- /dev/null +++ b/package-list.json @@ -0,0 +1,58 @@ +{ + "columns": [ + "Package", + "Version", + "Deps", + "CI", + "Coverage" + ], + "rows": [ + "Files", + ["ipfs/js-ipfs-unixfs-engine", "ipfs-unixfs-engine"], + + "DAG", + ["ipld/js-ipld", "ipld"], + ["ipld/js-ipld-dag-pb", "ipld-dag-pb"], + ["ipld/js-ipld-dag-cbor", "ipld-dag-cbor"], + + "Repo", + ["ipfs/js-ipfs-repo", "ipfs-repo"], + + "Exchange", + ["ipfs/js-ipfs-block-service", "ipfs-block-service"], + ["ipfs/js-ipfs-bitswap", "ipfs-bitswap"], + + "libp2p", + ["libp2p/js-libp2p", "libp2p"], + ["libp2p/js-libp2p-circuit", "libp2p-circuit"], + ["libp2p/js-libp2p-floodsub", "libp2p-floodsub"], + ["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"], + ["libp2p/js-libp2p-mdns", "libp2p-mdns"], + ["libp2p/js-libp2p-mplex", "libp2p-mplex"], + ["libp2p/js-libp2p-railing", "libp2p-railing"], + ["libp2p/js-libp2p-secio", "libp2p-secio"], + ["libp2p/js-libp2p-tcp", "libp2p-tcp"], + ["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"], + ["libp2p/js-libp2p-websocket-star", "libp2p-websocket-star"], + ["libp2p/js-libp2p-websockets", "libp2p-websockets"], + + "Data Types", + ["ipfs/js-ipfs-block", "ipfs-block"], + ["ipfs/js-ipfs-unixfs", "ipfs-unixfs"], + ["libp2p/js-peer-id", "peer-id"], + ["libp2p/js-peer-info", "peer-info"], + ["multiformats/js-multiaddr", "multiaddr"], + ["multiformats/js-multihash", "multihashes"], + + "Crypto", + ["libp2p/js-libp2p-crypto", "libp2p-crypto"], + ["libp2p/js-libp2p-keychain", "libp2p-keychain"], + + "Generics/Utils", + ["ipfs/js-ipfs-api", "ipfs-api"], + ["ipfs/ipfs-multipart", "ipfs-multipart"], + ["ipfs/is-ipfs", "is-ipfs"], + ["multiformats/js-multihashing", "multihashing"], + ["multiformats/js-mafmt", "mafmt"] + ] +} diff --git a/scripts/generate-package-table-for-readme.js b/scripts/generate-package-table-for-readme.js deleted file mode 100755 index a5252cb122..0000000000 --- a/scripts/generate-package-table-for-readme.js +++ /dev/null @@ -1,115 +0,0 @@ -#! /usr/bin/env node - -// This script generates the table of packages you can see in the readme - -// Columns to show at the header of the table -const columns = [ - 'Package', - 'Version', - 'Deps', - 'CI', - 'Coverage' -] - -// Headings are a string -// Arrays are packages. Index 0 is the GitHub repo and index 1 is the npm package -const rows = [ - 'Files', - ['ipfs/js-ipfs-unixfs-engine', 'ipfs-unixfs-engine'], - - 'DAG', - ['ipld/js-ipld', 'ipld'], - ['ipld/js-ipld-dag-pb', 'ipld-dag-pb'], - ['ipld/js-ipld-dag-cbor', 'ipld-dag-cbor'], - - 'Repo', - ['ipfs/js-ipfs-repo', 'ipfs-repo'], - - 'Exchange', - ['ipfs/js-ipfs-block-service', 'ipfs-block-service'], - ['ipfs/js-ipfs-bitswap', 'ipfs-bitswap'], - - 'libp2p', - ['libp2p/js-libp2p', 'libp2p'], - ['libp2p/js-libp2p-circuit', 'libp2p-circuit'], - ['libp2p/js-libp2p-floodsub', 'libp2p-floodsub'], - ['libp2p/js-libp2p-kad-dht', 'libp2p-kad-dht'], - ['libp2p/js-libp2p-mdns', 'libp2p-mdns'], - ['libp2p/js-libp2p-mplex', 'libp2p-mplex'], - ['libp2p/js-libp2p-railing', 'libp2p-railing'], - ['libp2p/js-libp2p-secio', 'libp2p-secio'], - ['libp2p/js-libp2p-tcp', 'libp2p-tcp'], - ['libp2p/js-libp2p-webrtc-star', 'libp2p-webrtc-star'], - ['libp2p/js-libp2p-websocket-star', 'libp2p-websocket-star'], - ['libp2p/js-libp2p-websockets', 'libp2p-websockets'], - - 'Data Types', - ['ipfs/js-ipfs-block', 'ipfs-block'], - ['ipfs/js-ipfs-unixfs', 'ipfs-unixfs'], - ['libp2p/js-peer-id', 'peer-id'], - ['libp2p/js-peer-info', 'peer-info'], - ['multiformats/js-multiaddr', 'multiaddr'], - ['multiformats/js-multihash', 'multihashes'], - - 'Crypto', - ['libp2p/js-libp2p-crypto', 'libp2p-crypto'], - ['libp2p/js-libp2p-keychain', 'libp2p-keychain'], - - 'Generics/Utils', - ['ipfs/js-ipfs-api', 'ipfs-api'], - ['ipfs/ipfs-multipart', 'ipfs-multipart'], - ['ipfs/is-ipfs', 'is-ipfs'], - ['multiformats/js-multihashing', 'multihashing'], - ['multiformats/js-mafmt', 'mafmt'] -] - -const isItemPackage = (item) => { - return Array.isArray(item) -} - -const packageBadges = [ - // Package - (gh, npm) => `[\`${npm}\`](//github.com/${gh})`, - // Version - (gh, npm) => `[![npm](https://img.shields.io/npm/v/${npm}.svg?maxAge=86400&style=flat)](//github.com/${gh}/releases)`, - // Deps - (gh, npm) => `[![Deps](https://david-dm.org/${gh}.svg?style=flat)](https://david-dm.org/${gh})`, - // CI - (gh, npm) => { - // Need to fix the path for jenkins links, as jenkins adds `/job/` between everything - const jenkinsPath = gh.split('/').join('/job/') - return `[![jenkins](https://ci.ipfs.team/buildStatus/icon?job=${gh}/master)](https://ci.ipfs.team/job/${jenkinsPath}/job/master/)` - }, - // Coverage - (gh, npm) => `[![codecov](https://codecov.io/gh/${gh}/branch/master/graph/badge.svg)](https://codecov.io/gh/${gh})` -] - -// Creates the table row for a package -const generatePackageRow = (item) => { - const row = packageBadges.map((func) => { - // First string is GitHub path, second is npm package name - return func(item[0], item[1]) - }).join(' | ') - const fullRow = `| ${row} |` - return fullRow -} - -// Generates a row for the table, depending if it's a package or a heading -const generateRow = (item) => { - if (isItemPackage(item)) { - return generatePackageRow(item) - } else { - return `| **${item}** |` - } -} - -const header = `| ${columns.join(' | ')} |` -const hr = `| ${columns.map(() => '---------').join('|')} |` - -const toPrint = [ - header, - hr, - rows.map((row) => generateRow(row)).join('\n') -] - -toPrint.forEach((t) => console.log(t)) From dbf17a329ba4697ddf038b7ca8d457645e097077 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 23 Aug 2018 20:13:10 +0100 Subject: [PATCH 47/51] chore: update ipfs-unixfs-engine dependency (#1523) * chore: update ipfs-unixfs-engine dependency * fix: test for cid-version=1 and raw-leaves=true * fix: do not default raw leaves option, add CID version test cases The raw leaves option should not be defaulted to false as its value is set to true by unixfs-engine if CID version is set and the user has not specified a value for raw leaves. Also adds tests cases for CID version option with different file sizes. * fix: dnslink for ipfs.io has changed License: MIT Signed-off-by: Alan Shaw --- package.json | 2 +- src/cli/commands/files/add.js | 1 - test/cli/dns.js | 2 +- test/cli/files.js | 48 ++++++++++++++---- .../greater-than-default-max-chunk-size | Bin 0 -> 300000 bytes .../fixtures/less-than-default-max-chunk-size | Bin 0 -> 888 bytes 6 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 test/fixtures/greater-than-default-max-chunk-size create mode 100644 test/fixtures/less-than-default-max-chunk-size diff --git a/package.json b/package.json index 60d3d8de17..66d2c0dd5c 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "ipfs-multipart": "~0.1.0", "ipfs-repo": "~0.23.1", "ipfs-unixfs": "~0.1.15", - "ipfs-unixfs-engine": "~0.32.1", + "ipfs-unixfs-engine": "~0.32.3", "ipld": "~0.17.3", "ipld-dag-cbor": "~0.12.1", "ipld-dag-pb": "~0.14.6", diff --git a/src/cli/commands/files/add.js b/src/cli/commands/files/add.js index 2547150e91..37732b7ad2 100644 --- a/src/cli/commands/files/add.js +++ b/src/cli/commands/files/add.js @@ -145,7 +145,6 @@ module.exports = { }, 'raw-leaves': { type: 'boolean', - default: false, describe: 'Use raw blocks for leaf nodes. (experimental)' }, 'cid-version': { diff --git a/test/cli/dns.js b/test/cli/dns.js index ae66c6c8c3..0c6ab4e1a5 100644 --- a/test/cli/dns.js +++ b/test/cli/dns.js @@ -16,7 +16,7 @@ describe('dns', () => runOnAndOff((thing) => { this.timeout(60 * 1000) return ipfs('dns ipfs.io').then((res) => { - expect(res.substr(0, 6)).to.eql('/ipfs/') + expect(res.substr(0, 6)).to.eql('/ipns/') }) }) })) diff --git a/test/cli/files.js b/test/cli/files.js index fda9c2de94..69843145e6 100644 --- a/test/cli/files.js +++ b/test/cli/files.js @@ -205,33 +205,63 @@ describe('files', () => runOnAndOff((thing) => { }) }) - it('add with cid-version=1', function () { + it('add with cid-version=1 < default max chunk size', function () { this.timeout(30 * 1000) - return ipfs('add src/init-files/init-docs/readme --cid-version=1') + return ipfs('add test/fixtures/less-than-default-max-chunk-size --cid-version=1') .then((out) => { expect(out) - .to.eql('added zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7 readme\n') + .to.eql('added zb2rhh5LdXumxQfNZCqV8pmcC56LX71ERgf2qCNQsmZnwYYx9 less-than-default-max-chunk-size\n') }) }) - it('add with cid-version=1 and raw-leaves=false', function () { + it('add with cid-version=1 > default max chunk size', function () { this.timeout(30 * 1000) - return ipfs('add src/init-files/init-docs/readme --cid-version=1 --raw-leaves=false') + return ipfs('add test/fixtures/greater-than-default-max-chunk-size --cid-version=1') .then((out) => { expect(out) - .to.eql('added zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7 readme\n') + .to.eql('added zdj7WbyyZoWVifUHUe58SNS184PpN8qAuCP6HpAY91iA8CveT greater-than-default-max-chunk-size\n') }) }) - it('add with cid-version=1 and raw-leaves=true', function () { + it('add with cid-version=1 and raw-leaves=false < default max chunk size', function () { this.timeout(30 * 1000) - return ipfs('add src/init-files/init-docs/readme --cid-version=1 --raw-leaves=true') + return ipfs(`add test/fixtures/less-than-default-max-chunk-size --cid-version=1 --raw-leaves=false`) .then((out) => { expect(out) - .to.eql('added zdj7WiLc855B1KPRgV7Fh8ivjuAhePE1tuJafmxH5HmmSjqaD readme\n') + .to.eql('added zdj7WWPWpmpFkrWJBhUEZ4QkGumsFsEdkaaEGs7U4dzJraogp less-than-default-max-chunk-size\n') + }) + }) + + it('add with cid-version=1 and raw-leaves=false > default max chunk size', function () { + this.timeout(30 * 1000) + + return ipfs(`add test/fixtures/greater-than-default-max-chunk-size --cid-version=1 --raw-leaves=false`) + .then((out) => { + expect(out) + .to.eql('added zdj7WmYojH6vMkDQFNDNwUy2ZawrggqAhS6jjRJwb1C4KXZni greater-than-default-max-chunk-size\n') + }) + }) + + it('add with cid-version=1 and raw-leaves=true < default max chunk size', function () { + this.timeout(30 * 1000) + + return ipfs('add test/fixtures/less-than-default-max-chunk-size --cid-version=1 --raw-leaves=true') + .then((out) => { + expect(out) + .to.eql('added zb2rhh5LdXumxQfNZCqV8pmcC56LX71ERgf2qCNQsmZnwYYx9 less-than-default-max-chunk-size\n') + }) + }) + + it('add with cid-version=1 and raw-leaves=true > default max chunk size', function () { + this.timeout(30 * 1000) + + return ipfs('add test/fixtures/greater-than-default-max-chunk-size --cid-version=1 --raw-leaves=true') + .then((out) => { + expect(out) + .to.eql('added zdj7WbyyZoWVifUHUe58SNS184PpN8qAuCP6HpAY91iA8CveT greater-than-default-max-chunk-size\n') }) }) diff --git a/test/fixtures/greater-than-default-max-chunk-size b/test/fixtures/greater-than-default-max-chunk-size new file mode 100644 index 0000000000000000000000000000000000000000..27347e687b813304b558bb49d330720d49c83035 GIT binary patch literal 300000 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�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%0Ahgb@Fn$^JKEaWqB| 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 Y1Z8*@8cC+$x&IEri4E%G@@#S24W%1Lxc~qF literal 0 HcmV?d00001 From d338f6a8420517ee9099348ce54031fbca54b8f5 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 23 Aug 2018 20:17:20 +0100 Subject: [PATCH 48/51] docs: better start and stop examples (#1521) This PR adds separate examples for each of the different methods of starting/stopping a node (promises, callbacks, events) instead of a single confusing example with all the methods in one code snippet. This makes for easier copy/paste. Also switches promise method to use async/await syntax. License: MIT Signed-off-by: Alan Shaw --- README.md | 140 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 7c3ea29a76..db57d78ba1 100644 --- a/README.md +++ b/README.md @@ -355,35 +355,55 @@ Start listening for connections with other IPFS nodes on the network. In most ca This method is asynchronous. There are several ways to be notified when the node has finished starting: 1. If you call `node.start()` with no arguments, it returns a promise. -2. If you pass a function as the final argument, it will be called when the node is started. *(Note: this method will **not** return a promise if you use a callback function.)* + + ```js + const node = new IPFS({ start: false }) + + node.on('ready', async () => { + console.log('Node is ready to use!') + + try { + await node.start() + console.log('Node started!') + } catch (error) { + console.error('Node failed to start!', error) + } + }) + ``` + +2. If you pass a function as the final argument, it will be called when the node is started (Note: this method will **not** return a promise if you use a callback function). + + ```js + const node = new IPFS({ start: false }) + + node.on('ready', () => { + console.log('Node is ready to use!') + + node.start(error => { + if (error) { + return console.error('Node failed to start!', error) + } + console.log('Node started!') + }) + }) + ``` + 3. You can listen for the [`start` event](#events). -```js -const node = new IPFS({ start: false }) + ```js + const node = new IPFS({ start: false }) -node.on('ready', () => { - console.log('Node is ready to use!') - - // Use a promise: - node.start() - .then(() => console.log('Node started!')) - .catch(error => console.error('Node failed to start!', error)) - - // OR use a callback: - node.start(error => { - if (error) { - console.error('Node failed to start!', error) - return - } - console.log('Node started!') - }) + node.on('ready', () => { + console.log('Node is ready to use!') + node.start() + }) - // OR use events: - node.on('error', error => console.error('Something went terribly wrong!', error)) - node.on('start', () => console.log('Node started!')) - node.start() -}) -``` + node.on('error', error => { + console.error('Something went terribly wrong!', error) + }) + + node.on('start', () => console.log('Node started!')) + ``` #### `node.stop([callback])` @@ -392,33 +412,55 @@ Close and stop listening for connections with other IPFS nodes, then release acc This method is asynchronous. There are several ways to be notified when the node has completely stopped: 1. If you call `node.stop()` with no arguments, it returns a promise. -2. If you pass a function as the final argument, it will be called when the node is stopped. *(Note: this method will **not** return a promise if you use a callback function.)* + + ```js + const node = new IPFS() + + node.on('ready', async () => { + console.log('Node is ready to use!') + + try { + await node.stop() + console.log('Node stopped!') + } catch (error) { + console.error('Node failed to stop cleanly!', error) + } + }) + ``` + +2. If you pass a function as the final argument, it will be called when the node is stopped (Note: this method will **not** return a promise if you use a callback function). + + ```js + const node = new IPFS() + + node.on('ready', () => { + console.log('Node is ready to use!') + + node.stop(error => { + if (error) { + return console.error('Node failed to stop cleanly!', error) + } + console.log('Node stopped!') + }) + }) + ``` + 3. You can listen for the [`stop` event](#events). -```js -const node = new IPFS() -node.on('ready', () => { - console.log('Node is ready to use!') - - // Stop with a promise: - node.stop() - .then(() => console.log('Node stopped!')) - .catch(error => console.error('Node failed to stop cleanly!', error)) - - // OR use a callback: - node.stop(error => { - if (error) { - console.error('Node failed to stop cleanly!', error) - return - } - console.log('Node stopped!') - }) + ```js + const node = new IPFS() - // OR use events: - node.on('error', error => console.error('Something went terribly wrong!', error)) - node.stop() -}) -``` + node.on('ready', () => { + console.log('Node is ready to use!') + node.stop() + }) + + node.on('error', error => { + console.error('Something went terribly wrong!', error) + }) + + node.on('stop', () => console.log('Node stopped!')) + ``` #### Core API From 3caa1ebc69e940400abbde4c44975e75e88a3afa Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 23 Aug 2018 20:17:46 +0100 Subject: [PATCH 49/51] docs: add pin API doc links (#1524) License: MIT Signed-off-by: Alan Shaw --- README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index db57d78ba1..9e407143c8 100644 --- a/README.md +++ b/README.md @@ -470,7 +470,7 @@ The IPFS core API provides all functionality that is not specific to setting up The core API is grouped into several areas: -#### `Files` +#### Files - [files](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md) - [`ipfs.files.add(data, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesadd). Alias to `ipfs.add`. @@ -503,7 +503,7 @@ The core API is grouped into several areas: - [`ipfs.block.put(block, cid, [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/BLOCK.md#put) - [`ipfs.block.stat(cid, [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/BLOCK.md#stat) -#### `Graph` +#### Graph - [dag](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/DAG.md) - [`ipfs.dag.put(dagNode, options, callback)`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/DAG.md#dagput) @@ -521,9 +521,13 @@ The core API is grouped into several areas: - [`ipfs.object.patch.rmLink(multihash, DAGLink, [options, callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/OBJECT.md#objectpatchrmlink) - [`ipfs.object.patch.appendData(multihash, data, [options, callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/OBJECT.md#objectpatchappenddata) - [`ipfs.object.patch.setData(multihash, data, [options, callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/OBJECT.md#objectpatchsetdata) -- [pin (not implemented, yet!)](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/) -#### `Crypto and Key Management` +- [pin](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PIN.md) + - [`ipfs.pin.add(hash, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PIN.md#pinadd) + - [`ipfs.pin.ls([hash], [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PIN.md#pinls) + - [`ipfs.pin.rm(hash, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PIN.md#pinrm) + +#### Crypto and Key Management - [key](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/KEY.md) - `ipfs.key.export(name, password, [callback])` @@ -534,7 +538,7 @@ The core API is grouped into several areas: - `ipfs.key.rm(name, [callback])` - [crypto (not yet implemented)](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC) -#### `Network` +#### Network - [bootstrap](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/) - `ipfs.bootstrap.list` @@ -566,7 +570,7 @@ The core API is grouped into several areas: - [`ipfs.swarm.disconnect(addr, [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/SWARM.md#disconnect) - [`ipfs.swarm.peers([opts] [, callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/SWARM.md#peers) -#### `Node Management` +#### Node Management - [miscellaneous operations](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/MISCELLANEOUS.md) - [`ipfs.id([callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/MISCELLANEOUS.md#id) @@ -595,7 +599,7 @@ The core API is grouped into several areas: - [`ipfs.config.set(key, value, [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/CONFIG.md#configset) - [`ipfs.config.replace(config, [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/CONFIG.md#configreplace) -#### `Domain data types` +#### Domain data types A set of data types are exposed directly from the IPFS instance under `ipfs.types`. That way you're not required to import/require the following. @@ -609,7 +613,7 @@ A set of data types are exposed directly from the IPFS instance under `ipfs.type - [`ipfs.types.dagPB`](https://github.com/ipld/js-ipld-dag-pb) - [`ipfs.types.dagCBOR`](https://github.com/ipld/js-ipld-dag-cbor) -#### `Util` +#### Util A set of utils are exposed directly from the IPFS instance under `ipfs.util`. That way you're not required to import/require the following: From 1fb71f2fe9182e95db913504ed7b933d3b7ca4a4 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 23 Aug 2018 20:35:31 +0100 Subject: [PATCH 50/51] docs: usage clarifications License: MIT Signed-off-by: Alan Shaw --- README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e407143c8..8693353cdc 100644 --- a/README.md +++ b/README.md @@ -103,11 +103,16 @@ This project is tested on OSX & Linux, expected to work on Windows. ### Use in Node.js -To include this project programmatically: +To create an IPFS node programmatically: ```js const IPFS = require('ipfs') const node = new IPFS() + +node.on('ready', () => { + // Ready to use! + // See https://github.com/ipfs/js-ipfs#core-api +}) ``` ### Through command line tool @@ -115,7 +120,7 @@ const node = new IPFS() In order to use js-ipfs as a CLI, you must install it with the `global` flag. Run the following (even if you have ipfs installed locally): ```bash -> npm install ipfs --global +npm install ipfs --global ``` The CLI is available by using the command `jsipfs` in your terminal. This is aliased, instead of using `ipfs`, to make sure it does not conflict with the [Go implementation](https://github.com/ipfs/go-ipfs). @@ -136,7 +141,19 @@ You can also load it using a ` ``` -Inserting one of the above lines will make an `Ipfs` object available in the global namespace. + +Inserting one of the above lines will make an `Ipfs` object available in the global namespace: + +```html + +``` ## Usage From 4f805d386e46ffec61a783c3f79910879e2918dd Mon Sep 17 00:00:00 2001 From: Dan Ordille Date: Fri, 24 Aug 2018 06:53:36 -0400 Subject: [PATCH 51/51] feat: Add option to specify chunking algorithm when adding files (#1469) This allows the chunking algorithm, and options to be specified when using the adding files. Specifying chunker and options are identical to go-ipfs and support the following formats: default, size-{size}, rabin, rabin-{avg}, rabin-{min}-{avg}-{max} This is required to achieve parity with go-ipfs. Fixes #1283 License: MIT Signed-off-by: Dan Ordille --- src/cli/commands/files/add.js | 7 ++- src/core/components/files.js | 11 ++++- src/core/utils.js | 81 +++++++++++++++++++++++++++++++++ src/http/api/resources/files.js | 6 ++- test/core/utils.js | 67 +++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 5 deletions(-) diff --git a/src/cli/commands/files/add.js b/src/cli/commands/files/add.js index 37732b7ad2..e9f87a81d5 100644 --- a/src/cli/commands/files/add.js +++ b/src/cli/commands/files/add.js @@ -135,6 +135,10 @@ module.exports = { default: false, describe: 'Only chunk and hash, do not write' }, + chunker: { + default: 'size-262144', + describe: 'Chunking algorithm to use, formatted like [size-{size}, rabin, rabin-{avg}, rabin-{min}-{avg}-{max}]' + }, 'enable-sharding-experiment': { type: 'boolean', default: false @@ -194,7 +198,8 @@ module.exports = { onlyHash: argv.onlyHash, hashAlg: argv.hash, wrapWithDirectory: argv.wrapWithDirectory, - pin: argv.pin + pin: argv.pin, + chunker: argv.chunker } if (options.enableShardingExperiment && utils.isDaemonOn()) { diff --git a/src/core/components/files.js b/src/core/components/files.js index de0b01c562..d575d893fa 100644 --- a/src/core/components/files.js +++ b/src/core/components/files.js @@ -18,6 +18,7 @@ const OtherBuffer = require('buffer').Buffer const CID = require('cids') const toB58String = require('multihashes').toB58String const errCode = require('err-code') +const parseChunkerString = require('../utils').parseChunkerString const WRAPPER = 'wrapper/' @@ -148,12 +149,18 @@ class AddHelper extends Duplex { } module.exports = function files (self) { - function _addPullStream (options) { + function _addPullStream (options = {}) { + let chunkerOptions + try { + chunkerOptions = parseChunkerString(options.chunker) + } catch (err) { + return pull.map(() => { throw err }) + } const opts = Object.assign({}, { shardSplitThreshold: self._options.EXPERIMENTAL.sharding ? 1000 : Infinity - }, options) + }, options, chunkerOptions) if (opts.hashAlg && opts.cidVersion !== 1) { opts.cidVersion = 1 diff --git a/src/core/utils.js b/src/core/utils.js index 55ac9be2a2..5c66f76c94 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -110,5 +110,86 @@ const resolvePath = promisify(function (objectAPI, ipfsPaths, callback) { }, callback) }) +/** + * Parses chunker string into options used by DAGBuilder in ipfs-unixfs-engine + * + * + * @param {String} chunker Chunker algorithm supported formats: + * "size-{size}" + * "rabin" + * "rabin-{avg}" + * "rabin-{min}-{avg}-{max}" + * + * @return {Object} Chunker options for DAGBuilder + */ +function parseChunkerString (chunker) { + if (!chunker) { + return { + chunker: 'fixed' + } + } else if (chunker.startsWith('size-')) { + const sizeStr = chunker.split('-')[1] + const size = parseInt(sizeStr) + if (isNaN(size)) { + throw new Error('Chunker parameter size must be an integer') + } + return { + chunker: 'fixed', + chunkerOptions: { + maxChunkSize: size + } + } + } else if (chunker.startsWith('rabin')) { + return { + chunker: 'rabin', + chunkerOptions: parseRabinString(chunker) + } + } else { + throw new Error(`Unrecognized chunker option: ${chunker}`) + } +} + +/** + * Parses rabin chunker string + * + * @param {String} chunker Chunker algorithm supported formats: + * "rabin" + * "rabin-{avg}" + * "rabin-{min}-{avg}-{max}" + * + * @return {Object} rabin chunker options + */ +function parseRabinString (chunker) { + const options = {} + const parts = chunker.split('-') + switch (parts.length) { + case 1: + options.avgChunkSize = 262144 + break + case 2: + options.avgChunkSize = parseChunkSize(parts[1], 'avg') + break + case 4: + options.minChunkSize = parseChunkSize(parts[1], 'min') + options.avgChunkSize = parseChunkSize(parts[2], 'avg') + options.maxChunkSize = parseChunkSize(parts[3], 'max') + break + default: + throw new Error('Incorrect chunker format (expected "rabin" "rabin-[avg]" or "rabin-[min]-[avg]-[max]"') + } + + return options +} + +function parseChunkSize (str, name) { + let size = parseInt(str) + if (isNaN(size)) { + throw new Error(`Chunker parameter ${name} must be an integer`) + } + + return size +} + exports.parseIpfsPath = parseIpfsPath exports.resolvePath = resolvePath +exports.parseChunkerString = parseChunkerString diff --git a/src/http/api/resources/files.js b/src/http/api/resources/files.js index 0370c4b0c2..3e2fb28d38 100644 --- a/src/http/api/resources/files.js +++ b/src/http/api/resources/files.js @@ -157,7 +157,8 @@ exports.add = { 'raw-leaves': Joi.boolean(), 'only-hash': Joi.boolean(), pin: Joi.boolean().default(true), - 'wrap-with-directory': Joi.boolean() + 'wrap-with-directory': Joi.boolean(), + chunker: Joi.string() }) // TODO: Necessary until validate "recursive", "stream-channels" etc. .options({ allowUnknown: true }) @@ -221,7 +222,8 @@ exports.add = { onlyHash: request.query['only-hash'], hashAlg: request.query['hash'], wrapWithDirectory: request.query['wrap-with-directory'], - pin: request.query.pin + pin: request.query.pin, + chunker: request.query.chunker } const aborter = abortable() diff --git a/test/core/utils.js b/test/core/utils.js index b5c84b15c1..af73c30af6 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -157,4 +157,71 @@ describe('utils', () => { }) }) }) + + describe('parseChunkerString', () => { + it('handles an empty string', () => { + const options = utils.parseChunkerString('') + expect(options).to.have.property('chunker').to.equal('fixed') + }) + + it('handles a null chunker string', () => { + const options = utils.parseChunkerString(null) + expect(options).to.have.property('chunker').to.equal('fixed') + }) + + it('parses a fixed size string', () => { + const options = utils.parseChunkerString('size-512') + expect(options).to.have.property('chunker').to.equal('fixed') + expect(options) + .to.have.property('chunkerOptions') + .to.have.property('maxChunkSize') + .to.equal(512) + }) + + it('parses a rabin string without size', () => { + const options = utils.parseChunkerString('rabin') + expect(options).to.have.property('chunker').to.equal('rabin') + expect(options) + .to.have.property('chunkerOptions') + .to.have.property('avgChunkSize') + }) + + it('parses a rabin string with only avg size', () => { + const options = utils.parseChunkerString('rabin-512') + expect(options).to.have.property('chunker').to.equal('rabin') + expect(options) + .to.have.property('chunkerOptions') + .to.have.property('avgChunkSize') + .to.equal(512) + }) + + it('parses a rabin string with min, avg, and max', () => { + const options = utils.parseChunkerString('rabin-42-92-184') + expect(options).to.have.property('chunker').to.equal('rabin') + expect(options).to.have.property('chunkerOptions') + expect(options.chunkerOptions).to.have.property('minChunkSize').to.equal(42) + expect(options.chunkerOptions).to.have.property('avgChunkSize').to.equal(92) + expect(options.chunkerOptions).to.have.property('maxChunkSize').to.equal(184) + }) + + it('throws an error for unsupported chunker type', () => { + const fn = () => utils.parseChunkerString('fake-512') + expect(fn).to.throw(Error) + }) + + it('throws an error for incorrect format string', () => { + const fn = () => utils.parseChunkerString('fixed-abc') + expect(fn).to.throw(Error) + }) + + it('throws an error for incorrect rabin format string', () => { + let fn = () => utils.parseChunkerString('rabin-1-2-3-4') + expect(fn).to.throw(Error) + }) + + it('throws an error for non integer rabin parameters', () => { + const fn = () => utils.parseChunkerString('rabin-abc') + expect(fn).to.throw(Error) + }) + }) })