diff --git a/package.json b/package.json index 6b0d6ec3..120c66d9 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ "fs": false, "fs-extra": false, "joi": false, - "./src/utils/repo/nodejs.js": "./src/utils/repo/browser.js", + "./src/utils/repo.js": "./src/utils/repo.browser.js", "./src/utils/exec.js": false, "./src/utils/find-ipfs-executable.js": false, - "./src/utils/tmp-dir.js": "./src/utils/tmp-dir-browser.js", + "./src/utils/tmp-dir.js": "./src/utils/tmp-dir.browser.js", "./src/utils/run.js": false, "./src/factory-daemon.js": false, "./src/ipfsd-daemon.js": false, @@ -62,29 +62,30 @@ "daemon" ], "dependencies": { - "@hapi/boom": "^7.4.7", + "@hapi/boom": "^8.0.1", "@hapi/hapi": "^18.3.2", - "@hapi/joi": "^15.1.1", + "@hapi/joi": "^16.1.7", "debug": "^4.1.1", - "execa": "^2.0.4", + "execa": "^3.1.0", "fs-extra": "^8.1.0", "hat": "~0.0.3", - "ipfs-http-client": "^38.2.0", + "ipfs-http-client": "^39.0.0", "ipfs-utils": "^0.4.0", "merge-options": "^1.0.1", "multiaddr": "^6.1.1", "safe-json-stringify": "^1.2.0", - "superagent": "^5.0.5" + "superagent": "^5.0.5", + "tempy": "^0.3.0" }, "devDependencies": { - "aegir": "^20.3.2", + "aegir": "^20.4.1", "chai": "^4.2.0", "delay": "^4.3.0", "detect-port": "^1.3.0", "dirty-chai": "^2.0.1", "go-ipfs-dep": "~0.4.22", - "husky": "^3.0.8", - "ipfs": "~0.38.0", + "husky": "^3.0.9", + "ipfs": "~0.39.0-rc.1", "is-running": "^2.1.0", "lint-staged": "^9.4.2", "proxyquire": "^2.1.3", diff --git a/src/endpoint/routes.js b/src/endpoint/routes.js index 13b58423..625d5e35 100644 --- a/src/endpoint/routes.js +++ b/src/endpoint/routes.js @@ -3,15 +3,14 @@ const hat = require('hat') const Joi = require('@hapi/joi') const boom = require('@hapi/boom') -const merge = require('merge-options') const FactoryDaemon = require('../factory-daemon') const tmpDir = require('../utils/tmp-dir') -const routeConfig = { +const routeOptions = { validate: { - query: { + query: Joi.object({ id: Joi.string().alphanum().required() - } + }) } } @@ -30,7 +29,7 @@ module.exports = (server) => { handler: async (request) => { const type = request.query.type || 'go' try { - return { tmpDir: await tmpDir(type === 'js') } + return { tmpDir: await tmpDir(type) } } catch (err) { throw boom.badRequest(err.message) } @@ -67,10 +66,10 @@ module.exports = (server) => { const payload = request.payload || {} // TODO: use the ../src/index.js so that the right Factory is picked - const f = new FactoryDaemon({ type: payload.type }) + const f = new FactoryDaemon(payload) try { - const ipfsd = await f.spawn(payload) + const ipfsd = await f.spawn(payload.ipfsOptions) const id = hat() nodes[id] = ipfsd @@ -80,7 +79,7 @@ module.exports = (server) => { gatewayAddr: ipfsd.gatewayAddr ? ipfsd.gatewayAddr.toString() : '', initialized: ipfsd.initialized, started: ipfsd.started, - _env: ipfsd._env, + _env: ipfsd.env, path: ipfsd.path } } catch (err) { @@ -100,7 +99,7 @@ module.exports = (server) => { const payload = request.payload || {} try { - await nodes[id].init(payload.initOpts) + await nodes[id].init(payload.opts) return { initialized: nodes[id].initialized @@ -109,7 +108,7 @@ module.exports = (server) => { throw boom.badRequest(err.message) } }, - config: routeConfig + options: routeOptions }) /* @@ -120,11 +119,9 @@ module.exports = (server) => { path: '/start', handler: async (request) => { const id = request.query.id - const payload = request.payload || {} - const flags = payload.flags || [] try { - await nodes[id].start(flags) + await nodes[id].start() return { apiAddr: nodes[id].apiAddr.toString(), @@ -134,7 +131,7 @@ module.exports = (server) => { throw boom.badRequest(err.message) } }, - config: routeConfig + options: routeOptions }) /* @@ -148,7 +145,7 @@ module.exports = (server) => { return { apiAddr: nodes[id].apiAddr.toString() } }, - config: routeConfig + options: routeOptions }) /* @@ -163,7 +160,7 @@ module.exports = (server) => { return { getawayAddr: nodes[id].gatewayAddr.toString() } }, - config: routeConfig + options: routeOptions }) /* @@ -185,7 +182,7 @@ module.exports = (server) => { throw boom.badRequest(err.message) } }, - config: routeConfig + options: routeOptions }) /* @@ -206,7 +203,7 @@ module.exports = (server) => { throw boom.badRequest(err.message) } }, - config: routeConfig + options: routeOptions }) /* @@ -230,7 +227,7 @@ module.exports = (server) => { throw boom.badRequest(err.message) } }, - config: routeConfig + options: routeOptions }) /* @@ -244,7 +241,7 @@ module.exports = (server) => { return { pid: nodes[id].pid } }, - config: routeConfig + options: routeOptions }) /* @@ -267,13 +264,14 @@ module.exports = (server) => { throw boom.badRequest(err.message) } }, - config: merge(routeConfig, { + options: { validate: { - query: { + query: Joi.object({ + id: Joi.string().alphanum().required(), key: Joi.string().optional() - } + }) } - }) + } }) /* @@ -295,13 +293,14 @@ module.exports = (server) => { return h.response().code(200) }, - config: merge(routeConfig, { + options: { validate: { - payload: { + query: Joi.object({ + id: Joi.string().alphanum().required(), key: Joi.string(), value: Joi.any() - } + }) } - }) + } }) } diff --git a/src/factory-client.js b/src/factory-client.js index 4b4bfec1..97190912 100644 --- a/src/factory-client.js +++ b/src/factory-client.js @@ -4,45 +4,54 @@ const request = require('superagent') const DaemonClient = require('./ipfsd-client') const merge = require('merge-options') const defaultConfig = require('./defaults/config.json') +// const findBin = require('./utils/find-ipfs-executable') -/** @ignore @typedef {import("./index").SpawnOptions} SpawnOptions */ +/** @ignore @typedef {import("./index").IpfsOptions} IpfsOptions */ +/** @ignore @typedef {import("./index").FactoryOptions} FactoryOptions */ /** * Exposes the same Factory API but uses a remote endpoint to create the Daemons/Nodes - * @param {Object} options */ class FactoryClient { - constructor (options) { - options = options || {} - if (!options.host) { options.host = 'localhost' } - if (!options.port) { options.port = 43134 } - if (!options.type) { options.type = 'go' } - if (typeof options.host === 'number') { - options.port = options.host - options.host = 'localhost' - } - - this.options = options - - if (options.type === 'proc') { - throw new Error('\'proc\' is not supported in client mode') - } + /** + * @param {FactoryOptions} options + */ + constructor (options = {}) { + /** @type FactoryOptions */ + this.options = merge({ + host: 'localhost', + port: 43134, + secure: false, + type: 'go', + defaultAddrs: false, + disposable: true, + env: process.env, + args: [], + ipfsHttp: { + path: require.resolve('ipfs-http-client'), + ref: require('ipfs-http-client') + }, + ipfsApi: { + path: require.resolve('ipfs'), + ref: require('ipfs') + } + // ipfsBin: findBin(options.type || 'go') + }, options) - this.baseUrl = `${options.secure ? 'https://' : 'http://'}${options.host}:${options.port}` + this.baseUrl = `${this.options.secure ? 'https://' : 'http://'}${this.options.host}:${this.options.port}` } /** * Utility method to get a temporary directory * useful in browsers to be able to generate temp * repos manually - * @param {boolean} isJS * * @returns {Promise} */ - async tmpDir (isJS) { + async tmpDir () { const res = await request .get(`${this.baseUrl}/util/tmp-dir`) - .query({ type: isJS ? 'js' : 'go' }) + .query({ type: this.options.type }) return res.body.tmpDir } @@ -50,15 +59,12 @@ class FactoryClient { /** * Get the version of the IPFS Daemon. * - * @param {Object} [options={}] * @returns {Promise} */ - async version (options = {}) { - options = options || { type: this.options.type } - + async version () { const res = await request .get(`${this.baseUrl}/version`) - .query(options) + .query({ type: this.options.type }) return res.body.version } @@ -66,32 +72,40 @@ class FactoryClient { /** * Spawn a remote daemon using ipfs-http-client * - * @param {SpawnOptions} [options={}] + * @param {IpfsOptions} [options={}] - Same as js-ipfs https://github.com/ipfs/js-ipfs#ipfs-constructor * @return {Promise} */ async spawn (options = {}) { - const daemonOptions = merge({ - exec: this.options.exec, - type: this.options.type, - IpfsClient: this.options.IpfsClient, - disposable: true, - start: options.disposable !== false, - init: options.disposable !== false, + const ipfsOptions = merge({ + start: this.options.disposable !== false, + init: this.options.disposable !== false, config: defaultConfig - }, options) + }, + this.options.ipfsOptions, + options) - if (options.defaultAddrs) { - delete daemonOptions.config.Addresses + if (this.options.defaultAddrs) { + delete ipfsOptions.config.Addresses } const res = await request .post(`${this.baseUrl}/spawn`) - .send(daemonOptions) + .send(merge( + { + ipfsOptions + }, + this.options + )) const ipfsd = new DaemonClient( this.baseUrl, res.body, - daemonOptions + merge( + { + ipfsOptions + }, + this.options + ) ) return ipfsd diff --git a/src/factory-daemon.js b/src/factory-daemon.js index d92ebeab..124a8837 100644 --- a/src/factory-daemon.js +++ b/src/factory-daemon.js @@ -4,22 +4,44 @@ const tmpDir = require('./utils/tmp-dir') const Daemon = require('./ipfsd-daemon') const merge = require('merge-options') const defaultConfig = require('./defaults/config.json') +const findBin = require('./utils/find-ipfs-executable') +const { defaultRepo } = require('./utils/repo') +const fs = require('fs') -/** @ignore @typedef {import("./index").SpawnOptions} SpawnOptions */ +/** @ignore @typedef {import("./index").IpfsOptions} IpfsOptions */ +/** @ignore @typedef {import("./index").FactoryOptions} FactoryOptions */ /** * Creates an instance of FactoryDaemon. - * - * @param {Object} options - * @param {string} [options.type='go'] - 'go' or 'js' - * @param {string} [options.exec] - the path of the daemon executable */ class FactoryDaemon { + /** + * @param {FactoryOptions} options + */ constructor (options) { - if (options && options.type === 'proc') { - throw new Error('This Factory does not know how to spawn in proc nodes') - } - this.options = merge({ type: 'go' }, options) + /** @type FactoryOptions */ + this.options = merge({ + host: 'localhost', + port: 43134, + secure: false, + type: 'go', + defaultAddrs: false, + disposable: true, + env: process.env, + args: [], + ipfsHttp: { + path: require.resolve('ipfs-http-client'), + ref: require('ipfs-http-client') + }, + ipfsApi: { + path: require.resolve('ipfs'), + ref: require('ipfs') + }, + ipfsBin: findBin(options.type || 'go') + }, options) + + this.options.ipfsHttp.path = fs.realpathSync(this.options.ipfsHttp.path) + this.options.ipfsApi.path = fs.realpathSync(this.options.ipfsApi.path) } /** @@ -27,19 +49,15 @@ class FactoryDaemon { * useful in browsers to be able to generate temp * repos manually * - * *Here for completeness* - * - * @param {String} type - the type of the node * @returns {Promise} */ - tmpDir (type) { - return Promise.resolve(tmpDir(type === 'js')) + tmpDir () { + return Promise.resolve(tmpDir(this.options.type)) } /** * Get the version of the IPFS Daemon. * - * @param {Object} [options={}] * @returns {Promise} - Resolves to `version` that might be one of the following: * - if type is `go` a version string like `ipfs version ` * - if type is `js` a version string like `js-ipfs version ` @@ -48,48 +66,55 @@ class FactoryDaemon { * - repo - the repo version * - commit - the commit hash for this version */ - version (options = {}) { - options = Object.assign( - { IpfsClient: this.options.IpfsClient }, - options, - { type: this.options.type, exec: this.options.exec } - ) + version () { // TODO: (1) this should check to see if it is looking for Go or JS // TODO: (2) This spawns a whole daemon just to get his version? There is // a way to get the version while the daemon is offline... - const d = new Daemon(options) + const d = new Daemon(merge({ + ipfsOptions: { + start: true, + init: true, + config: defaultConfig, + repo: tmpDir(this.options.type) + } + }, this.options)) return d.version() } /** * Spawn an IPFS node, either js-ipfs or go-ipfs * - * @param {SpawnOptions} [options={}] - Various config options and ipfs config parameters + * @param {IpfsOptions} [options={}] - Various config options and ipfs config parameters * @returns {Promise} */ async spawn (options = {}) { - const daemonOptions = merge({ - exec: this.options.exec, - type: this.options.type, - IpfsClient: this.options.IpfsClient, - disposable: true, - start: options.disposable !== false, - init: options.disposable !== false, - config: defaultConfig - }, options) + const ipfsOptions = merge( + { + start: this.options.disposable !== false, + init: this.options.disposable !== false, + config: defaultConfig, + repo: this.options.disposable + ? tmpDir(this.options.type) + : defaultRepo(this.options.type) + }, + this.options.ipfsOptions, + options + ) - if (options.defaultAddrs) { - delete daemonOptions.config.Addresses + if (this.options.defaultAddrs) { + delete ipfsOptions.config.Addresses } - const node = new Daemon(daemonOptions) + const node = new Daemon(merge({ + ipfsOptions + }, this.options)) - if (daemonOptions.init) { - await node.init(daemonOptions.initOptions) + if (ipfsOptions.init) { + await node.init(ipfsOptions.init) } - if (daemonOptions.start) { - await node.start(daemonOptions.args) + if (ipfsOptions.start) { + await node.start() } return node diff --git a/src/factory-in-proc.js b/src/factory-in-proc.js index 66fd2d78..516e2761 100644 --- a/src/factory-in-proc.js +++ b/src/factory-in-proc.js @@ -4,23 +4,39 @@ const tmpDir = require('./utils/tmp-dir') const InProc = require('./ipfsd-in-proc') const merge = require('merge-options') const defaultConfig = require('./defaults/config.json') +const { defaultRepo } = require('./utils/repo') -/** @ignore @typedef {import("./index").SpawnOptions} SpawnOptions */ +/** @ignore @typedef {import("./index").IpfsOptions} IpfsOptions */ +/** @ignore @typedef {import("./index").FactoryOptions} FactoryOptions */ /** * Factory to spawn in-proc JS-IPFS instances (aka in process nodes) - * @class - * @param {Object} options - * @param {String} [options.type='proc'] - one of 'go', 'js' or 'proc', in this case this needs to be 'proc' - * @param {String} [options.exec] - the path of the daemon executable or IPFS class in the case of `proc` */ class FactoryInProc { + /** + * @param {FactoryOptions} options + */ constructor (options) { - options = options || {} - if (options.type !== 'proc') { - throw new Error('This Factory only knows how to create in proc nodes') - } - this.options = options + /** @type FactoryOptions */ + this.options = merge({ + host: 'localhost', + port: 43134, + secure: false, + type: 'js', + defaultAddrs: false, + disposable: true, + env: process.env, + args: [], + ipfsHttp: { + path: require.resolve('ipfs-http-client'), + ref: require('ipfs-http-client') + }, + ipfsApi: { + path: require.resolve('ipfs'), + ref: require('ipfs') + } + // ipfsBin: findBin(options.type || 'js') + }, options) } /** @@ -28,22 +44,26 @@ class FactoryInProc { * useful in browsers to be able to generate temp * repos manually * - * *Here for completeness* - * * @returns {Promise} */ tmpDir () { - return Promise.resolve(tmpDir(true)) + return Promise.resolve(tmpDir(this.options.type)) } /** * Get the version of the currently used go-ipfs binary. * - * @param {Object} [options={}] * @returns {Promise} */ - async version (options = {}) { - const node = new InProc(options) + async version () { + const node = new InProc(merge({ + ipfsOptions: { + start: true, + init: true, + config: defaultConfig, + repo: tmpDir(this.options.type) + } + }, this.options)) const v = await node.version() return v } @@ -51,37 +71,36 @@ class FactoryInProc { /** * Spawn JSIPFS instances * - * @param {SpawnOptions} [options={}] - various config options and ipfs config parameters + * @param {IpfsOptions} [options={}] - various config options and ipfs config parameters * @returns {Promise} - Resolves to an array with an `ipfs-instance` attached to the node and a `Node` */ async spawn (options = {}) { - const daemonOptions = merge({ - exec: this.options.exec, - type: this.options.type, - IpfsApi: this.options.IpfsApi, - disposable: true, - start: options.disposable !== false, - init: options.disposable !== false, + const ipfsOptions = merge({ + silent: true, + start: this.options.disposable !== false, + init: this.options.disposable !== false, config: defaultConfig, - silent: true - }, options) - - if (options.defaultAddrs) { - delete daemonOptions.config.Addresses - } + repo: this.options.disposable + ? tmpDir(this.options.type) + : defaultRepo(this.options.type) + }, + this.options.ipfsOptions, + options) - if (typeof daemonOptions.exec !== 'function') { - throw new Error('\'type\' proc requires \'exec\' to be a coderef') + if (this.options.defaultAddrs) { + delete ipfsOptions.config.Addresses } - const node = new InProc(daemonOptions) + const node = new InProc(merge({ + ipfsOptions + }, this.options)) - if (daemonOptions.init) { - await node.init(daemonOptions.initOptions) + if (ipfsOptions.init) { + await node.init(ipfsOptions.init) } - if (daemonOptions.start) { - await node.start(daemonOptions.args) + if (ipfsOptions.start) { + await node.start() } return node diff --git a/src/index.js b/src/index.js index 035ba179..abfbbcc4 100644 --- a/src/index.js +++ b/src/index.js @@ -17,7 +17,10 @@ const Server = require('./endpoint/server') * @returns {(FactoryDaemon|FactoryInProc|FactoryClient)} */ const create = (opts) => { - const options = merge({ remote: !isNode }, opts) + /** @type FactoryOptions */ + const options = merge({ + remote: !isNode + }, opts) if (options.type === 'proc') { return new FactoryInProc(options) @@ -58,20 +61,20 @@ const createTestsInterface = (createOptions = {}) => { const createNode = async (options = {}) => { // Create factory with merged options const ipfsFactory = create(merge( - options.factoryOptions, - createOptions.factoryOptions - )) - - // Spawn with merged options - const node = await ipfsFactory.spawn(merge( { - initOptions: { profile: 'test' }, - preload: { enabled: false } + ipfsOptions: { + init: { profiles: ['test'] }, + preload: { enabled: false } + + } }, - options.spawnOptions, - createOptions.spawnOptions + createOptions, + options )) + // Spawn with merged options + const node = await ipfsFactory.spawn() + // Add `peerId` const id = await node.api.id() node.api.peerId = id @@ -125,34 +128,48 @@ module.exports = { */ /** - * @typedef {Object} SpawnOptions - * @property {Boolean} [init=true] - Should the node be initialized. - * @property {Object} initOptions - Should be of the form `{bits: }`, which sets the desired key size. - * @property {Boolean} [start=true] - Should the node be started. - * @property {string} [repoPath] - The repository path to use for this node, ignored if node is disposable. - * @property {Boolean} [disposable=true] - A new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits. - * @property {Boolean} [defaultAddrs=false] - Use the daemon default Swarm addrs. - * @property {String[]} [args] - Array of cmd line arguments to be passed to the IPFS daemon. - * @property {Object} [config] - IPFS configuration options. {@link https://github.com/ipfs/js-ipfs#optionsconfig IPFS config} - * @property {Object} env - Additional environment variables, passed to executing shell. Only applies for Daemon controllers. - * + * Same as https://github.com/ipfs/js-ipfs/blob/master/README.md#ipfs-constructor + * @typedef {Object} IpfsOptions + * @property {string|Object} [repo] - 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 instance. + * @property {boolean|Object} [init=true] - Initialize the repo when creating the IPFS node. Instead of a boolean, you may provide an object with custom initialization options. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsinit + * @property {boolean} [start=true] - If false, do not automatically start the IPFS node. Instead, you’ll need to manually call node.start() yourself. + * @property {string} [pass=null] - A passphrase to encrypt/decrypt your keys. + * @property {boolean} [silent=false] - Prevents all logging output from the IPFS node. + * @property {object} [relay] - Configure circuit relay. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsrelay Default: `{ enabled: true, hop: { enabled: false, active: false } }` + * @property {object} [preload] - Configure remote preload nodes. The remote will preload content added on this node, and also attempt to preload objects requested by this node. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionspreload Default: `{ enabled: true, addresses: [...]` + * @property {object} [EXPERIMENTAL] - Enable and configure experimental features. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsexperimental Default: `{ ipnsPubsub: false, sharding: false }` + * @property {object} [config] - Modify the default IPFS node config. This object will be merged with the default config; it will not replace it. The default config is documented in the js-ipfs config file docs. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsconfig + * @property {object} [ipld] - Modify the default IPLD config. This object will be merged with the default config; it will not replace it. Check IPLD docs for more information on the available options. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsipld + * @property {object|function} [libp2p] - The libp2p option allows you to build your libp2p node by configuration, or via a bundle function. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionslibp2p + * @property {object} [connectionManager] - Configure the libp2p connection manager. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsconnectionmanager + * @property {Array} [profiles] - Profiles to transform the config. */ /** * @typedef {Object} FactoryOptions * @property {boolean} [remote] - Use remote endpoint to spawn the nodes. Defaults to `true` when not in node. * @property {number} [port=43134] - Remote endpoint port. (Defaults to 43134) - * @property {string} [exec] - IPFS executable path. ipfsd-ctl will attempt to locate it by default. - * If you desire to spawn js-ipfs instances in the same process, pass the reference to the module instead (e.g exec: `require('ipfs')`) + * @property {string} [host=localhost] - Remote endpoint host. (Defaults to localhost) + * @property {string} [secure=false] - Remote endpoint uses http or https. (Defaults to false) + * @property {Boolean} [defaultAddrs=false] - Use the daemon default Swarm addrs. + * @property {Boolean} [disposable=true] - A new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits. * @property {string} [type] - The daemon type, see below the options: * - go - spawn go-ipfs daemon * - js - spawn js-ipfs daemon - * - proc - spawn in-process js-ipfs instance. Needs to be called also with exec. Example: `IPFSFactory.create({type: 'proc', exec: require('ipfs') })`. - * @property {Object} [IpfsClient] - A custom IPFS API constructor to use instead of the packaged one `js-ipfs-http-client`. + * - proc - spawn in-process js-ipfs instance + * @property {Object} [ipfsHttp] - Setup IPFS HTTP client to be used by ctl. + * @property {Object} [ipfsHttp.ref] - Reference to a IPFS HTTP Client object. (defaults to the local require(`ipfs-http-client`)) + * @property {string} [ipfsHttp.path] - Path to a IPFS HTTP Client to be required. (defaults to the local require.resolve('ipfs-http-client')) + * @property {Object} [ipfsApi] - Setup IPFS API to be used by ctl. + * @property {Object} [ipfsApi.ref] - Reference to a IPFS API object. (defaults to the local require(`ipfs`)) + * @property {string} [ipfsApi.path] - Path to a IPFS API implementation to be required. (defaults to the local require.resolve('ipfs')) + * @property {String} [ipfsBin] - Path to a IPFS exectutable . (defaults to the local 'js-ipfs/src/bin/cli.js') + * @property {Object} [env] - Additional environment variables, passed to executing shell. Only applies for Daemon controllers. + * @property {IpfsOptions} [ipfsOptions] - Options for the IPFS instance */ /** * @typedef {Object} TestsInterfaceOptions * @property {FactoryOptions} [factoryOptions] - FactoryOptions - * @property {SpawnOptions} [spawnOptions] - SpawnOptions + * @property {SpawnOptions} [IpfsOptions] - SpawnOptions */ diff --git a/src/ipfsd-client.js b/src/ipfsd-client.js index 4e1ea904..d2d482de 100644 --- a/src/ipfsd-client.js +++ b/src/ipfsd-client.js @@ -1,19 +1,22 @@ 'use strict' const request = require('superagent') -const IpfsClient = require('ipfs-http-client') const multiaddr = require('multiaddr') +const merge = require('merge-options') + +/** @ignore @typedef {import("./index").FactoryOptions} FactoryOptions */ /** * Creates an instance of Client. - * - * @param {*} baseUrl - * @param {*} _id - * @param {*} initialized - * @param {*} options */ class Client { - constructor (baseUrl, remoteState, options = {}) { + /** + * + * @param {string} baseUrl + * @param {Object} remoteState + * @param {FactoryOptions} options + */ + constructor (baseUrl, remoteState, options) { this.options = options this.baseUrl = baseUrl this._id = remoteState._id @@ -34,7 +37,7 @@ class Client { setApi (addr) { if (addr) { this.apiAddr = multiaddr(addr) - this.api = (this.options.IpfsClient || IpfsClient)(addr) + this.api = (this.options.ipfsHttp.ref)(addr) this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } @@ -51,13 +54,11 @@ class Client { /** * Initialize a repo. * - * @param {Object} [initOptions] - * @param {number} [initOptions.keysize=2048] - The bit size of the identiy key. - * @param {string} [initOptions.directory=IPFS_PATH] - The location of the repo. + * @param {Object} [initOptions] - @see https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsinit * @returns {Promise} */ async init (initOptions) { - if (this.initialized && initOptions) { + if (this.initialized && typeof initOptions === 'object') { throw new Error(`Repo already initialized can't use different options, ${JSON.stringify(initOptions)}`) } if (this.initialized) { @@ -65,13 +66,18 @@ class Client { return this } - initOptions = initOptions || {} + const opts = merge( + { + emptyRepo: false, + bits: 2048 + }, + initOptions === true ? {} : initOptions + ) - // TODO probably needs to change config like the other impl const res = await request .post(`${this.baseUrl}/init`) .query({ id: this._id }) - .send({ initOptions }) + .send({ opts }) this.initialized = res.body.initialized @@ -100,17 +106,16 @@ class Client { /** * Start the daemon. * - * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. * @returns {Promise} */ - async start (flags = []) { + async start () { if (this.started) { return this.api } const res = await request .post(`${this.baseUrl}/start`) .query({ id: this._id }) - .send({ flags }) + .send() this.started = true diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.js index b734045f..e061878a 100644 --- a/src/ipfsd-daemon.js +++ b/src/ipfsd-daemon.js @@ -1,20 +1,15 @@ +/* eslint-disable no-async-promise-executor */ 'use strict' -const IpfsClient = require('ipfs-http-client') const multiaddr = require('multiaddr') const fs = require('fs-extra') -const path = require('path') const merge = require('merge-options') const debug = require('debug') -const os = require('os') -const hat = require('hat') +const execa = require('execa') +const tempy = require('tempy') const log = debug('ipfsd-ctl:daemon') const safeStringify = require('safe-json-stringify') -const tmpDir = require('./utils/tmp-dir') -const findIpfsExecutable = require('./utils/find-ipfs-executable') -const setConfigValue = require('./utils/set-config-value') -const run = require('./utils/run') -const { checkForRunningApi, defaultRepo } = require('./utils/repo/nodejs') +const { checkForRunningApi } = require('./utils/repo') const daemonLog = { info: debug('ipfsd-ctl:daemon:stdout'), @@ -35,6 +30,8 @@ function translateError (err) { throw err } +/** @ignore @typedef {import("./index").FactoryOptions} FactoryOptions */ + /** * ipfsd for a go-ipfs or js-ipfs daemon * Create a new node. @@ -43,35 +40,26 @@ function translateError (err) { class Daemon { /** * @constructor - * @param {Typedefs.SpawnOptions} [opts] + * @param {FactoryOptions} [opts] */ - constructor (opts = { type: 'go' }) { - const rootPath = process.env.testpath - ? process.env.testpath - : __dirname - + constructor (opts) { this.opts = opts - const envExec = this.opts.type === 'go' ? process.env.IPFS_GO_EXEC : process.env.IPFS_JS_EXEC - this.exec = this.opts.exec || envExec || findIpfsExecutable(this.opts.type, rootPath) - this._env = Object.assign({}, process.env, this.opts.env) - this.path = this.opts.disposable - ? tmpDir(this.opts.type === 'js') - : (this.opts.repoPath || defaultRepo(this.opts.type)) - this.bits = this.opts.initOptions ? this.opts.initOptions.bits : null - this.disposable = this.opts.disposable + this.path = this.opts.ipfsOptions.repo + this.exec = this.opts.ipfsBin + this.env = Object.assign({}, this.env, { IPFS_PATH: this.path }) + this.disposable = opts.disposable this.subprocess = null this.initialized = fs.existsSync(this.path) this.started = false this.clean = true this.apiAddr = null this.gatewayAddr = null - /** @member {IpfsClient} */ this.api = null } setApi (addr) { this.apiAddr = multiaddr(addr) - this.api = (this.opts.IpfsClient || IpfsClient)(addr) + this.api = require(this.opts.ipfsHttp.path)(addr) this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } @@ -82,77 +70,56 @@ class Daemon { this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } - /** - * Current repo path - * - * @member {string} - */ - get repoPath () { - return this.path - } - - /** - * Shell environment variables - * - * @member {object} - */ - get env () { - return this.path ? Object.assign({}, this._env, { IPFS_PATH: this.path }) : this._env - } - /** * Initialize a repo. * - * @param {Object} [initOptions={}] - * @param {number} [initOptions.bits=2048] - The bit size of the identiy key. - * @param {string} [initOptions.directory=IPFS_PATH] - The location of the repo. - * @param {string} [initOptions.pass] - The passphrase of the keychain. + * @param {Object} [initOptions={}] - @see https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsinit * @returns {Promise} */ async init (initOptions) { - if (this.initialized && initOptions) { + if (this.initialized && typeof initOptions === 'object') { throw new Error(`Repo already initialized can't use different options, ${JSON.stringify(initOptions)}`) } - if (this.initialized) { this.clean = false return this } - initOptions = initOptions || {} + const opts = merge( + { + emptyRepo: false, + bits: 2048 + }, + initOptions === true ? {} : initOptions + ) - if (initOptions.directory && initOptions.directory !== this.path) { - this.path = initOptions.directory - } - - const bits = initOptions.bits || this.bits const args = ['init'] - // do not just set a default keysize, - // in case we decide to change it at - // the daemon level in the future - if (bits) { + // bits + if (opts.bits) { args.push('-b') - args.push(bits) + args.push(opts.bits) } - if (initOptions.pass) { + + // pass + if (opts.pass) { args.push('--pass') - args.push('"' + initOptions.pass + '"') - } - if (initOptions.profile) { - // TODO: remove when JS IPFS supports profiles - if (this.opts.type === 'go') { - args.push('-p') - args.push(initOptions.profile) - } else { - log(`ignoring "profile" option, not supported for ${this.opts.type} node`) - } + args.push('"' + opts.pass + '"') } - await run(this, args, { env: this.env }) + // profiles + // if (opts.profiles && Array.isArray(opts.profiles)) { + // args.push('-p') + // args.push(opts.profiles.join(',')) + // } + + await execa(this.exec, args, { + env: this.env + }) .catch(translateError) + const conf = await this.getConfig() - await this.replaceConfig(merge(conf, this.opts.config)) + await this.replaceConfig(merge(conf, this.opts.ipfsOptions.config)) this.clean = false this.initialized = true @@ -177,11 +144,15 @@ class Daemon { /** * Start the daemon. * - * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. * @return {Promise} */ - start (flags = []) { - const args = ['daemon'].concat(flags || []) + start () { + let args = ['daemon'] + + if (this.opts.args.length > 0) { + args = args.concat(this.opts.args) + } + // Check if a daemon is already running const api = checkForRunningApi(this.path) @@ -192,42 +163,29 @@ class Daemon { } let output = '' + return new Promise(async (resolve, reject) => { + this.subprocess = execa(this.exec, args, { + env: this.env + }) + this.subprocess.stderr.on('data', data => daemonLog.err(data.toString())) + this.subprocess.stdout.on('data', data => { + daemonLog.info(data.toString()) + output += data + const apiMatch = output.trim().match(/API .*listening on:? (.*)/) + const gwMatch = output.trim().match(/Gateway .*listening on:? (.*)/) + + if (apiMatch && apiMatch.length > 0) { + this.setApi(apiMatch[1]) + } + + if (gwMatch && gwMatch.length > 0) { + this.setGateway(gwMatch[1]) + } - return new Promise(async (resolve, reject) => { // eslint-disable-line no-async-promise-executor - this.subprocess = run(this, args, { - env: this.env, - stderr: (data) => { - data = String(data) - - if (data) { - daemonLog.err(data.trim()) - } - }, - stdout: (data) => { - data = String(data) - - if (data) { - daemonLog.info(data.trim()) - } - - output += data - - const apiMatch = output.trim().match(/API .*listening on:? (.*)/) - const gwMatch = output.trim().match(/Gateway .*listening on:? (.*)/) - - if (apiMatch && apiMatch.length > 0) { - this.setApi(apiMatch[1]) - } - - if (gwMatch && gwMatch.length > 0) { - this.setGateway(gwMatch[1]) - } - - if (output.match(/(?:daemon is running|Daemon is ready)/)) { - // we're good - this.started = true - resolve(this.api) - } + if (output.match(/(?:daemon is running|Daemon is ready)/)) { + // we're good + this.started = true + resolve(this.api) } }) @@ -254,7 +212,6 @@ class Daemon { return this } await this.api.stop() - // TODO this should call this.api.stop await this.killProcess(timeout) if (this.disposable) { @@ -333,9 +290,12 @@ class Daemon { async getConfig (key = 'show') { const { stdout - } = await run(this, ['config', key], { - env: this.env - }) + } = await execa( + this.exec, + ['config', key], + { + env: this.env + }) .catch(translateError) if (key === 'show') { @@ -353,7 +313,11 @@ class Daemon { * @returns {Promise} */ setConfig (key, value) { - return setConfigValue(this, key, value) + return execa( + this.exec, + ['config', key, value, '--json'], + { env: this.env } + ) .catch(translateError) } @@ -364,10 +328,14 @@ class Daemon { * @returns {Promise} */ async replaceConfig (config) { - const tmpFile = path.join(os.tmpdir(), hat()) + const tmpFile = tempy.file() await fs.writeFile(tmpFile, safeStringify(config)) - await run(this, ['config', 'replace', `${tmpFile}`], { env: this.env }) + await execa( + this.exec, + ['config', 'replace', `${tmpFile}`], + { env: this.env } + ) .catch(translateError) await fs.unlink(tmpFile) } @@ -380,7 +348,7 @@ class Daemon { async version () { const { stdout - } = await run(this, ['version'], { + } = await execa(this.exec, ['version'], { env: this.env }) .catch(translateError) diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.js index f60c0210..594a29f6 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.js @@ -1,26 +1,24 @@ 'use strict' const multiaddr = require('multiaddr') -const IpfsClient = require('ipfs-http-client') const merge = require('merge-options') -const tmpDir = require('./utils/tmp-dir') -const { repoExists, removeRepo, checkForRunningApi, defaultRepo } = require('./utils/repo/nodejs') +const { repoExists, removeRepo, checkForRunningApi } = require('./utils/repo') + +/** @ignore @typedef {import("./index").IpfsOptions} IpfsOptions */ +/** @ignore @typedef {import("./index").FactoryOptions} FactoryOptions */ /** * ipfsd for a js-ipfs instance (aka in-process IPFS node) - * - * @param {Object} [opts] - * @param {Object} [opts.env={}] - Additional environment settings, passed to executing shell. */ class InProc { + /** + * @constructor + * @param {FactoryOptions} [opts] + */ constructor (opts = {}) { this.opts = opts - this.opts.args = this.opts.args || [] - this.path = this.opts.disposable - ? tmpDir(this.opts.type === 'js') - : (this.opts.repoPath || defaultRepo(this.opts.type)) - this.bits = this.opts.initOptions ? this.opts.initOptions.bits : null - this.disposable = this.opts.disposable + this.path = this.opts.ipfsOptions.repo + this.disposable = opts.disposable this.initialized = false this.started = false this.clean = true @@ -28,19 +26,19 @@ class InProc { this.gatewayAddr = null this.api = null - this.opts.EXPERIMENTAL = merge({ sharding: false }, opts.EXPERIMENTAL) + this.opts.ipfsOptions.EXPERIMENTAL = merge({ sharding: false }, opts.ipfsOptions.EXPERIMENTAL) this.opts.args.forEach((arg, index) => { if (arg === '--enable-sharding-experiment') { - this.opts.EXPERIMENTAL.sharding = true + this.opts.ipfsOptions.EXPERIMENTAL.sharding = true } else if (arg === '--enable-namesys-pubsub') { - this.opts.EXPERIMENTAL.ipnsPubsub = true + this.opts.ipfsOptions.EXPERIMENTAL.ipnsPubsub = true } else if (arg === '--enable-dht-experiment') { - this.opts.EXPERIMENTAL.dht = true + this.opts.ipfsOptions.EXPERIMENTAL.dht = true } else if (arg === '--offline') { - this.opts.offline = true + this.opts.ipfsOptions.offline = true } else if (arg.startsWith('--pass')) { - this.opts.pass = this.opts.args[index + 1] + this.opts.ipfsOptions.pass = this.opts.args[index + 1] } }) } @@ -50,28 +48,14 @@ class InProc { return this } - const IPFS = this.opts.exec - - this.api = await IPFS.create({ - repo: this.path, - init: false, - start: false, - pass: this.opts.pass, - EXPERIMENTAL: this.opts.EXPERIMENTAL, - libp2p: this.opts.libp2p, - config: this.opts.config, - silent: this.opts.silent, - relay: this.opts.relay, - preload: this.opts.preload, - ipld: this.opts.ipld, - connectionManager: this.opts.connectionManager - }) + const IPFS = this.opts.ipfsApi.ref + this.api = await IPFS.create(this.opts.ipfsOptions) return this } setApi (addr) { this.apiAddr = multiaddr(addr) - this.api = (this.opts.IpfsApi || IpfsClient)(addr) + this.api = (this.opts.ipfsHttp.ref)(addr) this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } @@ -82,36 +66,15 @@ class InProc { this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } - /** - * Get the current repo path - * - * @member {string} - */ - get repoPath () { - return this.path - } - - /** - * Is the environment - * - * @member {Object} - */ - get env () { - throw new Error('Not implemented!') - } - /** * Initialize a repo. * - * @param {Object} [initOptions={}] - * @param {number} [initOptions.bits=2048] - The bit size of the identiy key. - * @param {string} [initOptions.directory=IPFS_PATH] - The location of the repo. - * @param {string} [initOptions.pass] - The passphrase of the keychain. + * @param {Object} [initOptions={}] - @see https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsinit * @returns {Promise} */ async init (initOptions) { const initialized = await repoExists(this.path) - if (initialized && initOptions) { + if (initialized && typeof initOptions === 'object') { throw new Error(`Repo already initialized can't use different options, ${JSON.stringify(initOptions)}`) } @@ -122,13 +85,21 @@ class InProc { } // Repo not initialized - initOptions = initOptions || {} + const opts = merge( + { + emptyRepo: false, + bits: 2048 + }, + initOptions === true ? {} : initOptions + ) await this.setExec() - await this.api.init(initOptions) + if (!this.opts.ipfsOptions.init) { + await this.api.init(opts) + } const conf = await this.getConfig() - await this.replaceConfig(merge(conf, this.opts.config)) + await this.replaceConfig(merge(conf, this.opts.ipfsOptions.config)) this.clean = false this.initialized = true return this @@ -164,7 +135,9 @@ class InProc { } await this.setExec() - await this.api.start() + if (!this.opts.ipfsOptions.start) { + await this.api.start() + } this.started = true return this.api diff --git a/src/utils/exec.js b/src/utils/exec.js deleted file mode 100644 index 46419542..00000000 --- a/src/utils/exec.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict' - -const debug = require('debug') -const log = debug('ipfsd-ctl:exec') -const path = require('path') -const execa = require('execa') -const noop = () => {} - -function exec (cmd, args, opts) { - opts = Object.assign({}, { - stdout: noop, - stderr: noop - }, opts) - - log(path.basename(cmd), args.join(' ')) - const command = execa(cmd, args, { env: opts.env }) - command.stderr.on('data', opts.stderr) - command.stdout.on('data', opts.stdout) - - return command -} - -module.exports = exec diff --git a/src/utils/find-ipfs-executable.js b/src/utils/find-ipfs-executable.js index cdee4667..26dd5b68 100644 --- a/src/utils/find-ipfs-executable.js +++ b/src/utils/find-ipfs-executable.js @@ -1,42 +1,14 @@ 'use strict' -const fs = require('fs') const os = require('os') const isWindows = os.platform() === 'win32' -const path = require('path') -module.exports = (type, rootPath) => { - const execPath = { - go: path.join('go-ipfs-dep', 'go-ipfs', isWindows ? 'ipfs.exe' : 'ipfs'), - js: path.join('ipfs', 'src', 'cli', 'bin.js') +module.exports = (type) => { + if (type === 'js') { + return process.env.IPFS_JS_EXEC || require.resolve('ipfs/src/cli/bin.js') } - let appRoot = rootPath ? path.join(rootPath, '..') : process.cwd() - // If inside .asar try to load from .asar.unpacked - // this only works if asar was built with - // asar --unpack-dir=node_modules/go-ipfs-dep/* (not tested) - // or - // electron-packager ./ --asar.unpackDir=node_modules/go-ipfs-dep - if (appRoot.includes(`.asar${path.sep}`)) { - appRoot = appRoot.replace(`.asar${path.sep}`, `.asar.unpacked${path.sep}`) + if (type === 'go') { + return process.env.IPFS_GO_EXEC || require.resolve(`go-ipfs-dep/go-ipfs/${isWindows ? 'ipfs.exe' : 'ipfs'}`) } - const depPath = execPath[type] - const npm3Path = path.join(appRoot, '../', depPath) - const npm2Path = path.join(appRoot, 'node_modules', depPath) - - if (fs.existsSync(npm3Path)) { - return npm3Path - } - - if (fs.existsSync(npm2Path)) { - return npm2Path - } - - try { - return require.resolve(depPath) - } catch (error) { - // ignore the error - } - - throw new Error('Cannot find the IPFS executable') } diff --git a/src/utils/flatten.js b/src/utils/flatten.js deleted file mode 100644 index 43b10471..00000000 --- a/src/utils/flatten.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict' - -// taken from https://github.com/hughsk/flat -module.exports = (target) => { - const output = {} - const step = (object, prev) => { - object = object || {} - Object.keys(object).forEach(function (key) { - const value = object[key] - const isarray = Array.isArray(value) - const type = Object.prototype.toString.call(value) - const isbuffer = Buffer.isBuffer(value) - const isobject = ( - type === '[object Object]' || - type === '[object Array]' - ) - - const newKey = prev - ? prev + '.' + key - : key - - if (!isarray && !isbuffer && isobject && Object.keys(value).length) { - return step(value, newKey) - } - - output[newKey] = value - }) - } - - step(target) - - return output -} diff --git a/src/utils/parse-config.js b/src/utils/parse-config.js deleted file mode 100644 index ac0703e0..00000000 --- a/src/utils/parse-config.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -const fs = require('fs-extra') -const path = require('path') - -module.exports = async (configPath) => { - const file = await fs.readFile(path.join(configPath, 'config')) - - return JSON.parse(file.toString()) -} diff --git a/src/utils/repo/browser.js b/src/utils/repo.browser.js similarity index 100% rename from src/utils/repo/browser.js rename to src/utils/repo.browser.js diff --git a/src/utils/repo/nodejs.js b/src/utils/repo.js similarity index 100% rename from src/utils/repo/nodejs.js rename to src/utils/repo.js diff --git a/src/utils/run.js b/src/utils/run.js deleted file mode 100644 index ad499505..00000000 --- a/src/utils/run.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict' - -const os = require('os') -const isWindows = os.platform() === 'win32' -const exec = require('./exec') - -module.exports = (node, args, opts = {}) => { - let executable = node.exec - - if (isWindows && executable.endsWith('.js')) { - args = args || [] - args.unshift(node.exec) - executable = process.execPath - } - - // Don't pass on arguments that were passed into the node executable - opts.execArgv = [] - - return exec(executable, args, opts) -} diff --git a/src/utils/set-config-value.js b/src/utils/set-config-value.js deleted file mode 100644 index 3f7ed0c9..00000000 --- a/src/utils/set-config-value.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -const run = require('./run') - -module.exports = (node, key, value) => { - return run( - node, - ['config', key, value, '--json'], - { env: node.env } - ) -} diff --git a/src/utils/tmp-dir-browser.js b/src/utils/tmp-dir.browser.js similarity index 61% rename from src/utils/tmp-dir-browser.js rename to src/utils/tmp-dir.browser.js index 3a0aa517..9649f7f3 100644 --- a/src/utils/tmp-dir-browser.js +++ b/src/utils/tmp-dir.browser.js @@ -2,8 +2,8 @@ const hat = require('hat') -module.exports = (isJs) => { - return (isJs +module.exports = (type) => { + return (type === 'js' ? 'jsipfs_' : 'ipfs_' ) + hat() diff --git a/src/utils/tmp-dir.js b/src/utils/tmp-dir.js index 41ecb095..12024d02 100644 --- a/src/utils/tmp-dir.js +++ b/src/utils/tmp-dir.js @@ -4,6 +4,6 @@ const os = require('os') const path = require('path') const hat = require('hat') -module.exports = (isJs) => { - return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${hat()}`) +module.exports = (type) => { + return path.join(os.tmpdir(), `${type === 'js' ? 'jsipfs' : 'ipfs'}_${hat()}`) } diff --git a/test/add-retrieve.spec.js b/test/add-retrieve.spec.js index 5659a1ad..f27afc15 100644 --- a/test/add-retrieve.spec.js +++ b/test/add-retrieve.spec.js @@ -6,12 +6,11 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) const IPFSFactory = require('../src') -const JSIPFS = require('ipfs') const tests = [ - { type: 'go', bits: 1024 }, - { type: 'js', bits: 512 }, - { type: 'proc', exec: JSIPFS, bits: 512 } + { type: 'go' }, + { type: 'js' }, + { type: 'proc' } ] describe('data can be put and fetched', function () { @@ -22,7 +21,7 @@ describe('data can be put and fetched', function () { before(async function () { const f = IPFSFactory.create(dfOpts) - ipfsd = await f.spawn({ initOptions: { profile: 'test' } }) + ipfsd = await f.spawn() expect(ipfsd).to.exist() expect(ipfsd.api).to.exist() diff --git a/test/api.spec.js b/test/api.spec.js deleted file mode 100644 index aee6265f..00000000 --- a/test/api.spec.js +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -const multiaddr = require('multiaddr') -const path = require('path') -const DaemonFactory = require('../src') -const { isNode } = require('ipfs-utils/src/env') - -const tests = [ - { type: 'go' }, - { type: 'js' } - // { type: 'proc', exec: JSIPFS } -] - -describe('ipfsd.api for Daemons', () => { - tests.forEach((dfOpts) => describe(`${dfOpts.type}`, () => { - const API_PORT = '5101' - const GW_PORT = '8180' - - const config = { - Addresses: { - API: `/ip4/127.0.0.1/tcp/${API_PORT}`, - Gateway: `/ip4/127.0.0.1/tcp/${GW_PORT}` - } - } - let df - - before(() => { - df = DaemonFactory.create(dfOpts) - }) - - it('test the ipfsd.api', async function () { - this.timeout(50 * 1000) - - // TODO skip in browser - can we avoid using file system operations here? - if (!isNode) { return this.skip() } - - const ipfsd = await df.spawn({ - start: false, - config: config, - initOptions: { bits: 1024, profile: 'test' } - }) - const api = await ipfsd.start() - - const res = await api.addFromFs(path.join(__dirname, 'fixtures/'), { - recursive: true - }) - - expect(res.length).to.equal(4) - - const fixuresDir = res.find(file => file.path === 'fixtures') - expect(fixuresDir).to.have.property( - 'hash', - 'QmNiv9nS9xipNafXqApzGqwajU8EaXrS2vUPJftg5ZEDUb' - ) - - const testFile = res.find(file => file.path === 'fixtures/test.txt') - expect(testFile).to.have.property( - 'hash', - 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' - ) - - await ipfsd.stop() - }) - - it('check if API and Gateway addrs are correct', async function () { - this.timeout(50 * 1000) - - const ipfsd = await df.spawn({ - config: config, - initOptions: { bits: 1024, profile: 'test' } - }) - - // Check for props in daemon - expect(ipfsd).to.have.property('apiAddr') - expect(ipfsd).to.have.property('gatewayAddr') - expect(ipfsd.apiAddr).to.not.be.null() - expect(multiaddr.isMultiaddr(ipfsd.apiAddr)).to.equal(true) - expect(ipfsd.gatewayAddr).to.not.be.null() - expect(multiaddr.isMultiaddr(ipfsd.gatewayAddr)).to.equal(true) - - // Check for props in ipfs-http-client instance - expect(ipfsd.api).to.have.property('apiHost') - expect(ipfsd.api).to.have.property('apiPort') - expect(ipfsd.api).to.have.property('gatewayHost') - expect(ipfsd.api).to.have.property('gatewayPort') - expect(ipfsd.api.apiHost).to.equal('127.0.0.1') - expect(ipfsd.api.apiPort).to.equal(API_PORT) - expect(ipfsd.api.gatewayHost).to.equal('127.0.0.1') - expect(ipfsd.api.gatewayPort).to.equal(GW_PORT) - - await ipfsd.stop() - }) - })) -}) diff --git a/test/custom-api.spec.js b/test/custom-api.spec.js deleted file mode 100644 index 37406b6e..00000000 --- a/test/custom-api.spec.js +++ /dev/null @@ -1,32 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -const IpfsClient = require('ipfs-http-client') -const IpfsFactory = require('../src') - -describe('custom API', function () { - this.timeout(30 * 1000) - - it('should create a factory with a custom API', async function () { - const mockApi = {} - - const f = IpfsFactory.create({ - type: 'js', - initOptions: { bits: 512 }, - IpfsClient: () => mockApi - }) - - const ipfsd = await f.spawn({ initOptions: { profile: 'test' } }) - - expect(ipfsd.api).to.equal(mockApi) - // Restore a real API so that the node can be stopped properly - ipfsd.api = IpfsClient(ipfsd.apiAddr) - await ipfsd.stop() - }) -}) diff --git a/test/endpoint/client.js b/test/endpoint/client.js index 314fbc48..a7cd1ab8 100644 --- a/test/endpoint/client.js +++ b/test/endpoint/client.js @@ -32,7 +32,7 @@ describe('client', () => { it('should handle valid request', async () => { mock.post('http://localhost:9999/spawn', (req) => { - expect(req.body.opt1).to.equal('hello!') + expect(req.body.ipfsOptions.opt1).to.equal('hello!') return { body: { _id: hat(), diff --git a/test/exec.node.js b/test/exec.node.js index 3946c6a3..c17786e7 100644 --- a/test/exec.node.js +++ b/test/exec.node.js @@ -8,7 +8,7 @@ const expect = chai.expect chai.use(dirtyChai) const isrunning = require('is-running') const path = require('path') -const exec = require('../src/utils/exec') +const execa = require('execa') const os = require('os') const delay = require('delay') @@ -49,24 +49,20 @@ describe('exec', () => { let stdout = '' let stderr = '' - await exec(process.execPath, [ + const p = execa(process.execPath, [ path.resolve(path.join(__dirname, 'fixtures', 'talky.js')) - ], { - stdout: (data) => { - stdout += String(data) - }, - stderr: (data) => { - stderr += String(data) - } - }) + ]) + p.stderr.on('data', d => (stderr += d.toString())) + p.stdout.on('data', d => (stdout += d.toString())) + await p expect(stdout).to.equal('hello\n') expect(stderr).to.equal('world\n') }) it('survives process errors and captures exit code and stderr', async () => { try { - await exec(process.execPath, [ + await execa(process.execPath, [ path.resolve(path.join(__dirname, 'fixtures', 'error.js')) ]) expect.fail('Should have errored') @@ -80,7 +76,7 @@ describe('exec', () => { const tok = token() const hang = 'tail -f /dev/null'.split(' ') const args = hang.concat(tok) - const p = exec(args[0], args.slice(1)) + const p = execa(args[0], args.slice(1)) let running = await psExpect(p.pid, true, 10) expect(running).to.be.ok() @@ -101,7 +97,7 @@ describe('exec', () => { it('SIGKILL kills survivor', async () => { const tok = token() - const p = exec(survivor, [tok], {}) + const p = execa(survivor, [tok], {}) let running = await psExpect(p.pid, true, 10) expect(running).to.be.ok() diff --git a/test/node.js b/test/node.js index de147cc1..b24f7026 100644 --- a/test/node.js +++ b/test/node.js @@ -3,6 +3,5 @@ 'use strict' require('./exec.node') -require('./npm-install.node') require('./start-stop.node') require('./endpoint') diff --git a/test/non-disposable.spec.js b/test/non-disposable.spec.js index a562cdab..b9ab8aed 100644 --- a/test/non-disposable.spec.js +++ b/test/non-disposable.spec.js @@ -11,9 +11,9 @@ const expect = chai.expect chai.use(dirtyChai) const tests = [ - { type: 'go', bits: 1024 }, - { type: 'js', bits: 512 }, - { type: 'proc', exec: require('ipfs'), bits: 512 } + { type: 'go', disposable: false }, + { type: 'js', disposable: false }, + { type: 'proc', disposable: false } ] tests.forEach((fOpts) => { @@ -38,8 +38,7 @@ tests.forEach((fOpts) => { const df = IPFSFactory.create(fOpts) try { await df.spawn({ - repoPath: daemon.path, - disposable: false, + repo: daemon.path, init: true }) throw new Error('Should throw') @@ -55,8 +54,7 @@ tests.forEach((fOpts) => { const df = IPFSFactory.create(fOpts) const ipfsd = await df.spawn({ - repoPath: daemon.path, - disposable: false, + repo: daemon.path, init: true, start: true }) @@ -71,8 +69,7 @@ tests.forEach((fOpts) => { } const df = IPFSFactory.create(fOpts) const ipfsd = await df.spawn({ - repoPath: daemon.path, - disposable: false, + repo: daemon.path, init: true }) @@ -83,11 +80,9 @@ tests.forEach((fOpts) => { it('should not init and start', async () => { const df = IPFSFactory.create(fOpts) - const path = await df.tmpDir(fOpts.type === 'js') + const path = await df.tmpDir(fOpts.type) const ipfsd = await df.spawn({ - initOptions: { bits: fOpts.bits }, - repoPath: path, - disposable: false + repo: path }) expect(ipfsd.api).to.not.exist() expect(ipfsd.initialized).to.be.false() @@ -98,11 +93,9 @@ tests.forEach((fOpts) => { it('should init and start', async () => { const df = IPFSFactory.create(fOpts) - const path = await df.tmpDir(fOpts.type === 'js') + const path = await df.tmpDir(fOpts.type) const ipfsd = await df.spawn({ - initOptions: { bits: fOpts.bits }, - repoPath: path, - disposable: false, + repo: path, start: true, init: true }) @@ -115,33 +108,25 @@ tests.forEach((fOpts) => { it('should only init', async () => { const df = IPFSFactory.create(fOpts) - const path = await df.tmpDir(fOpts.type === 'js') + const path = await df.tmpDir(fOpts.type) const ipfsd = await df.spawn({ - initOptions: { bits: fOpts.bits }, - repoPath: path, - disposable: false, + repo: path, init: true }) expect(ipfsd.initialized).to.be.true() expect(ipfsd.started).to.be.false() - // await ipfsd.stop() - // await ipfsd.cleanup() }) it('should only init manualy', async () => { const df = IPFSFactory.create(fOpts) - const path = await df.tmpDir(fOpts.type === 'js') + const path = await df.tmpDir(fOpts.type) const ipfsd = await df.spawn({ - initOptions: { bits: fOpts.bits }, - repoPath: path, - disposable: false + repo: path }) expect(ipfsd.initialized).to.be.false() await ipfsd.init() expect(ipfsd.initialized).to.be.true() expect(ipfsd.started).to.be.false() - // await ipfsd.stop() - // await ipfsd.cleanup() }) }) }) diff --git a/test/npm-install.node.js b/test/npm-install.node.js deleted file mode 100644 index c99e81bd..00000000 --- a/test/npm-install.node.js +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -const fs = require('fs-extra') -const path = require('path') -const os = require('os') -const isWindows = os.platform() === 'win32' - -const types = [ - 'js', - 'go' -] - -types.forEach((type) => { - describe('ipfs executable path', () => { - let tmp - let appName - let oldPath - - before(() => { - tmp = os.tmpdir() - - appName = type === 'js' - ? 'bin.js' - : isWindows ? 'ipfs.exe' : 'ipfs' - - oldPath = process.env.testpath - - // fake __dirname - process.env.testpath = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/lib') - }) - - after(() => { - process.env.testpath = oldPath - }) - - it('has the correct path when installed with npm3', async () => { - const execPath = type === 'js' - ? 'ipfsd-ctl-test/node_modules/ipfs/src/cli' - : 'ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs' - - const npm3Path = path.join(tmp, execPath) - - await fs.mkdirp(npm3Path) - fs.writeFileSync(path.join(npm3Path, appName)) - - delete require.cache[require.resolve('../src/ipfsd-daemon.js')] - const Daemon = require('../src/ipfsd-daemon.js') - - const node = new Daemon({ type }) - expect(node.exec) - .to.eql(path.join(tmp, `${execPath}/${appName}`)) - - await fs.remove(path.join(tmp, 'ipfsd-ctl-test')) - }) - - it('has the correct path when installed with npm2', async () => { - const execPath = type === 'js' - ? 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/ipfs/src/cli' - : 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs' - - const npm2Path = path.join(tmp, execPath) - - await fs.mkdirp(npm2Path) - fs.writeFileSync(path.join(npm2Path, appName)) - - delete require.cache[require.resolve('../src/ipfsd-daemon.js')] - const Daemon = require('../src/ipfsd-daemon.js') - - const node = new Daemon({ type }) - expect(node.exec) - .to.eql(path.join(tmp, `${execPath}/${appName}`)) - - await fs.remove(path.join(tmp, 'ipfsd-ctl-test')) - }) - }) -}) diff --git a/test/spawn-options.spec.js b/test/spawn-options.spec.js index ea27a514..9c79b724 100644 --- a/test/spawn-options.spec.js +++ b/test/spawn-options.spec.js @@ -4,18 +4,17 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') +const merge = require('merge-options') const expect = chai.expect chai.use(dirtyChai) const { isNode } = require('ipfs-utils/src/env') const hat = require('hat') const IPFSFactory = require('../src') -const JSIPFS = require('ipfs') -const { repoExists } = require('./../src/utils/repo/nodejs') const tests = [ - { type: 'go', bits: 1024 }, - { type: 'js', bits: 512 }, - { type: 'proc', exec: JSIPFS, bits: 512 } + { type: 'go' }, + { type: 'js' }, + { type: 'proc' } ] const jsVersion = require('ipfs/package.json').version @@ -37,10 +36,7 @@ describe('Spawn options', function () { }) it('f.version', async function () { - let version = await f.version({ - type: fOpts.type, - exec: fOpts.exec - }) + let version = await f.version() if (fOpts.type === 'proc') { version = version.version @@ -51,7 +47,7 @@ describe('Spawn options', function () { describe('spawn a node and attach api', () => { it('create init and start node', async function () { - const ipfsd = await f.spawn({ initOptions: { bits: fOpts.bits, profile: 'test' } }) + const ipfsd = await f.spawn() expect(ipfsd).to.exist() expect(ipfsd.api).to.exist() expect(ipfsd.api.id).to.exist() @@ -59,8 +55,7 @@ describe('Spawn options', function () { }) }) - // TODO re-enable when jenkins runs tests in isolation - describe.skip('spawn with default swarm addrs', () => { + describe('spawn with default swarm addrs', () => { const addrs = { go: [ '/ip4/0.0.0.0/tcp/4001', @@ -82,14 +77,8 @@ describe('Spawn options', function () { if (!isNode && fOpts.type === 'proc') { this.skip() } - - const ipfsd = await f.spawn({ - defaultAddrs: true, - initOptions: { - bits: fOpts.bits, - profile: 'test' - } - }) + const f = await IPFSFactory.create(merge(fOpts, { defaultAddrs: true })) + const ipfsd = await f.spawn() let config = await ipfsd.getConfig('Addresses.Swarm') @@ -118,11 +107,7 @@ describe('Spawn options', function () { } const ipfsd = await f.spawn({ - config: config, - initOptions: { - bits: fOpts.bits, - profile: 'test' - } + config: config }) const apiConfig = await ipfsd.getConfig('Addresses.API') expect(apiConfig).to.eql(addr) @@ -147,40 +132,14 @@ describe('Spawn options', function () { }) }) - describe('custom repo path', () => { - // We can only check if it really got created when run in Node.js - if (!isNode) { return } - - it('allows passing custom repo path to spawn', async function () { - const repoPath = await f.tmpDir(fOpts.type) - const options = { - disposable: false, - init: true, - start: true, - repoPath: repoPath, - initOptions: { bits: fOpts.bits, profile: 'test' } - } - - const ipfsd = await f.spawn(options) - const exists = await repoExists(repoPath) - expect(exists).to.be.true() - await ipfsd.stop() - await ipfsd.cleanup() - }) - }) - describe('f.spawn with args', () => { if (!isNode && fOpts.type !== 'proc') { return } it('check that pubsub was enabled', async () => { const topic = `test-topic-${hat()}` const data = Buffer.from('hey there') - const options = { - args: ['--enable-namesys-pubsub'], - initOptions: { bits: fOpts.bits, profile: 'test' } - } - - const ipfsd = await f.spawn(options) + const f = await IPFSFactory.create(merge(fOpts, { args: ['--enable-namesys-pubsub'] })) + const ipfsd = await f.spawn() return new Promise(async (resolve, reject) => { // eslint-disable-line no-async-promise-executor const handler = async (msg) => { @@ -207,12 +166,7 @@ describe('Spawn options', function () { let ipfsd before(async function () { - ipfsd = await f.spawn({ - initOptions: { - bits: fOpts.bits, - profile: 'test' - } - }) + ipfsd = await f.spawn() }) after(async function () { diff --git a/test/start-stop.node.js b/test/start-stop.node.js index a85f3497..0814d28d 100644 --- a/test/start-stop.node.js +++ b/test/start-stop.node.js @@ -9,6 +9,7 @@ chai.use(dirtyChai) const fs = require('fs') const path = require('path') +const merge = require('merge-options') const isrunning = require('is-running') const delay = require('delay') const findIpfsExecutable = require('../src/utils/find-ipfs-executable') @@ -18,8 +19,8 @@ const IPFSFactory = require('../src') const dfBaseConfig = require('./utils/df-config-nodejs') const tests = [ - { type: 'go', bits: 1024 }, - { type: 'js', bits: 512 } + { type: 'go' }, + { type: 'js' } ] tests.forEach((fOpts) => { @@ -40,10 +41,8 @@ tests.forEach((fOpts) => { const f = IPFSFactory.create(dfConfig) ipfsd = await f.spawn({ - init: true, - start: false, - disposable: true, - initOptions: { bits: fOpts.bits, profile: 'test' } + init: { profiles: ['test'] }, + start: false }) expect(ipfsd).to.exist() @@ -119,15 +118,15 @@ tests.forEach((fOpts) => { return this.skip() } - const df = IPFSFactory.create(dfConfig) + const df = IPFSFactory.create(merge({ args: ['--should-not-exist'] }, dfConfig)) const ipfsd = await df.spawn({ start: false, - initOptions: { bits: fOpts.bits, profile: 'test' } + init: { profiles: ['test'] } }) try { - await ipfsd.start(['--should-not-exist']) + await ipfsd.start() expect.fail('Should have errored') } catch (err) { expect(err.message).to.contain('unknown option "should-not-exist"') @@ -148,10 +147,8 @@ tests.forEach((fOpts) => { const f = IPFSFactory.create(dfConfig) ipfsd = await f.spawn({ - init: true, - start: false, - disposable: true, - initOptions: { bits: fOpts.bits, profile: 'test' } + init: { profiles: ['test'] }, + start: false }) expect(ipfsd).to.exist() @@ -215,15 +212,15 @@ tests.forEach((fOpts) => { return this.skip() } - const df = IPFSFactory.create(dfConfig) + const df = IPFSFactory.create(merge(dfConfig, { args: ['--should-not-exist'] })) const ipfsd = await df.spawn({ start: false, - initOptions: { bits: fOpts.bits, profile: 'test' } + init: { profiles: ['test'] } }) try { - await ipfsd.start(['--should-not-exist']) + await ipfsd.start() expect.fail('Should have errored') } catch (err) { expect(err.message).to.contain('unknown option "should-not-exist"') @@ -236,11 +233,11 @@ tests.forEach((fOpts) => { before(async function () { this.timeout(50 * 1000) - const df = IPFSFactory.create(dfConfig) + const df = IPFSFactory.create(merge({ ipfsBin: exec }, dfConfig)) ipfsd = await df.spawn({ exec, - initOptions: { bits: fOpts.bits, profile: 'test' } + initOptions: { profile: 'test' } }) expect(ipfsd).to.exist() @@ -269,8 +266,9 @@ tests.forEach((fOpts) => { process.env = Object.assign({}, process.env, fOpts.type === 'go' ? { IPFS_GO_EXEC: exec } : { IPFS_JS_EXEC: exec }) + ipfsd = await df.spawn({ - initOptions: { bits: fOpts.bits, profile: 'test' } + init: { profiles: ['test'] } }) expect(ipfsd).to.exist() @@ -294,14 +292,13 @@ tests.forEach((fOpts) => { let ipfsd before(async () => { - const df = IPFSFactory.create(dfConfig) - const exec = path.join('invalid', 'exec', 'ipfs') + const df = IPFSFactory.create(merge(dfConfig, { + ipfsBin: path.join('invalid', 'exec', 'ipfs') + })) ipfsd = await df.spawn({ - init: false, start: false, - exec: exec, - initOptions: { bits: fOpts.bits, profile: 'test' } + init: false }) expect(ipfsd).to.exist() @@ -313,7 +310,7 @@ tests.forEach((fOpts) => { it('should fail on init', async () => { try { - await ipfsd.init() + await ipfsd.init({ profile: 'test' }) expect.fail('Should have errored') } catch (err) { expect(err).to.exist() @@ -327,29 +324,19 @@ tests.forEach((fOpts) => { before(async function () { this.timeout(20 * 1000) - const f = IPFSFactory.create(dfConfig) + const f = IPFSFactory.create(merge({ disposable: false }, dfConfig)) ipfsd = await f.spawn({ init: false, start: false, - disposable: false, - repoPath: tempDir(fOpts.type), - initOptions: { - bits: fOpts.bits, - profile: 'test' - }, - config: { - Addresses: { - Swarm: ['/ip4/127.0.0.1/tcp/0'], - API: '/ip4/127.0.0.1/tcp/0', - Gateway: '/ip4/127.0.0.1/tcp/0' - } - } + repo: tempDir(fOpts.type) }) expect(ipfsd).to.exist() - await ipfsd.init() + await ipfsd.init({ + profile: 'test' + }) await ipfsd.start() }) diff --git a/test/utils.spec.js b/test/utils.spec.js index c22fbd6c..06734f52 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -10,35 +10,19 @@ chai.use(dirtyChai) const fs = require('fs') const { isNode } = require('ipfs-utils/src/env') const path = require('path') -const flatten = require('../src/utils/flatten') const tempDir = require('../src/utils/tmp-dir') const findIpfsExecutable = require('../src/utils/find-ipfs-executable') describe('utils', () => { - describe('.flatten', () => { - it('should flatten', () => { - expect(flatten({ a: { b: { c: [1, 2, 3] } } })) - .to.eql({ 'a.b.c': [1, 2, 3] }) - }) - - it('should handle nulls', () => { - expect(flatten(null)).to.eql({}) - }) - - it('should handle undefined', () => { - expect(flatten(undefined)).to.eql({}) - }) - }) - describe('.tempDir', () => { it('should create tmp directory path for go-ipfs', () => { - const tmpDir = tempDir() + const tmpDir = tempDir('go') expect(tmpDir).to.exist() expect(tmpDir).to.include('ipfs_') }) it('should create tmp directory path for js-ipfs', () => { - const tmpDir = tempDir(true) + const tmpDir = tempDir('js') expect(tmpDir).to.exist() expect(tmpDir).to.include('jsipfs_') })