From 20ea2f9ac4195b0598e0b57de37e418c11731a1d Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Fri, 19 Aug 2022 14:48:33 -0700 Subject: [PATCH 01/23] feat: add js-kubo-rpc-client see https://github.com/ipfs/kubo/issues/9125 --- .aegir.js | 4 +++- package.json | 9 +++++---- src/factory.js | 3 ++- src/ipfsd-client.js | 8 +++++++- src/ipfsd-daemon.js | 8 +++++++- src/ipfsd-in-proc.js | 9 ++++++++- src/types.ts | 4 ++++ 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.aegir.js b/.aegir.js index 8b6ca72d..6e57c500 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,6 +1,7 @@ import { createServer } from './src/index.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' +import * as kuboRpcModule from 'js-kubo-rpc-client' import * as goIpfsModule from 'go-ipfs' /** @type {import('aegir').Options["build"]["config"]} */ @@ -22,7 +23,8 @@ export default { before: async () => { const server = createServer(undefined, { ipfsModule, - ipfsHttpModule + ipfsHttpModule: process.env.USE_KUBO_JS ? undefined : ipfsHttpModule, + kuboRpcModule: process.env.USE_KUBO_JS ? kuboRpcModule : undefined }, { go: { ipfsBin: goIpfsModule.path() diff --git a/package.json b/package.json index 1faf5f6a..b12012e0 100644 --- a/package.json +++ b/package.json @@ -140,10 +140,10 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "test": "aegir test", - "test:node": "aegir test -t node", - "test:chrome": "aegir test -t browser", - "test:firefox": "aegir test -t browser -- --browser firefox", + "test": "aegir test && USE_KUBO_JS=1 aegir test", + "test:node": "aegir test -t node && USE_KUBO_JS=1 aegir test -t node", + "test:chrome": "aegir test -t browser && USE_KUBO_JS=1 aegir test -t browser", + "test:firefox": "aegir test -t browser -- --browser firefox && USE_KUBO_JS=1 aegir test -t browser -- --browser firefox", "release": "aegir release" }, "dependencies": { @@ -155,6 +155,7 @@ "execa": "^6.1.0", "ipfs-utils": "^9.0.1", "joi": "^17.2.1", + "js-kubo-rpc-client": "^1.0.0", "merge-options": "^3.0.1", "nanoid": "^4.0.0", "p-wait-for": "^4.1.0", diff --git a/src/factory.js b/src/factory.js index 81e13ba7..9f8f3e78 100644 --- a/src/factory.js +++ b/src/factory.js @@ -88,7 +88,8 @@ class Factory { remote: false, ipfsBin: undefined, ipfsModule: undefined, - ipfsHttpModule: undefined + ipfsHttpModule: undefined, + kuboRpcModule: undefined } } diff --git a/src/ipfsd-client.js b/src/ipfsd-client.js index 528ab016..0e06b719 100644 --- a/src/ipfsd-client.js +++ b/src/ipfsd-client.js @@ -95,7 +95,13 @@ class Client { http: this.apiAddr }) } else if (this.apiAddr) { - this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + if (this.opts.kuboRpcModule != null) { + this.api = this.opts.kuboRpcModule.create(this.apiAddr) + } else if (this.opts.ipfsHttpModule != null) { + this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + } else { + throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') + } } if (this.api) { diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.js index 9ad655f6..f9b3e84e 100644 --- a/src/ipfsd-daemon.js +++ b/src/ipfsd-daemon.js @@ -100,7 +100,13 @@ class Daemon { http: this.apiAddr }) } else if (this.apiAddr) { - this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + if (this.opts.kuboRpcModule != null) { + this.api = this.opts.kuboRpcModule.create(this.apiAddr) + } else if (this.opts.ipfsHttpModule != null) { + this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + } else { + throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') + } } if (!this.api) { diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.js index 15a6970e..8d21bf3a 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.js @@ -67,7 +67,14 @@ class InProc { */ _setApi (addr) { this.apiAddr = new Multiaddr(addr) - this.api = this.opts.ipfsHttpModule.create(addr) + + if (this.opts.kuboRpcModule != null) { + this.api = this.opts.kuboRpcModule.create(addr) + } else if (this.opts.ipfsHttpModule != null) { + this.api = this.opts.ipfsHttpModule.create(addr) + } else { + throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') + } this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } diff --git a/src/types.ts b/src/types.ts index fcc1d4af..bca76334 100644 --- a/src/types.ts +++ b/src/types.ts @@ -168,6 +168,10 @@ export interface ControllerOptions { * Reference to an ipfs-http-client module */ ipfsHttpModule?: any + /** + * Reference to a js-kubo-rpc-client module + */ + kuboRpcModule?: any /** * Reference to an ipfs or ipfs-core module */ From cb0e10bc80067fabc86c392c6b9ce9cbcb037f55 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Tue, 6 Sep 2022 13:23:59 -0700 Subject: [PATCH 02/23] chore: update kubo-rpc-client package name --- .aegir.js | 2 +- package.json | 2 +- src/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.aegir.js b/.aegir.js index 6e57c500..6b7e4f80 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,7 +1,7 @@ import { createServer } from './src/index.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -import * as kuboRpcModule from 'js-kubo-rpc-client' +import * as kuboRpcModule from 'kubo-rpc-client' import * as goIpfsModule from 'go-ipfs' /** @type {import('aegir').Options["build"]["config"]} */ diff --git a/package.json b/package.json index b12012e0..48ea7bfa 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "execa": "^6.1.0", "ipfs-utils": "^9.0.1", "joi": "^17.2.1", - "js-kubo-rpc-client": "^1.0.0", + "kubo-rpc-client": "^1.0.0", "merge-options": "^3.0.1", "nanoid": "^4.0.0", "p-wait-for": "^4.1.0", diff --git a/src/types.ts b/src/types.ts index bca76334..69b12bd8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -169,7 +169,7 @@ export interface ControllerOptions { */ ipfsHttpModule?: any /** - * Reference to a js-kubo-rpc-client module + * Reference to a kubo-rpc-client module */ kuboRpcModule?: any /** From 7fa9e7e8272ae4cab4e068a88ed9be8a815186fb Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:57:10 -0700 Subject: [PATCH 03/23] fix: use cross-env to set USE_KUBO_JS --- .aegir.js | 18 +++++++++++++----- package.json | 9 +++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.aegir.js b/.aegir.js index 6b7e4f80..c133ab01 100644 --- a/.aegir.js +++ b/.aegir.js @@ -21,11 +21,19 @@ export default { } }, before: async () => { - const server = createServer(undefined, { - ipfsModule, - ipfsHttpModule: process.env.USE_KUBO_JS ? undefined : ipfsHttpModule, - kuboRpcModule: process.env.USE_KUBO_JS ? kuboRpcModule : undefined - }, { + /** + * @type {import('./src/types.js').ControllerOptions} + */ + let controllerOptions = { + ipfsModule, + } + + if (process.env.USE_KUBO_JS) { + controllerOptions.kuboRpcModule = kuboRpcModule + } else { + controllerOptions.ipfsHttpModule = ipfsHttpModule + } + const server = createServer(undefined, controllerOptions, { go: { ipfsBin: goIpfsModule.path() }, diff --git a/package.json b/package.json index 48ea7bfa..a8b916a8 100644 --- a/package.json +++ b/package.json @@ -140,10 +140,10 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "test": "aegir test && USE_KUBO_JS=1 aegir test", - "test:node": "aegir test -t node && USE_KUBO_JS=1 aegir test -t node", - "test:chrome": "aegir test -t browser && USE_KUBO_JS=1 aegir test -t browser", - "test:firefox": "aegir test -t browser -- --browser firefox && USE_KUBO_JS=1 aegir test -t browser -- --browser firefox", + "test": "aegir test && cross-env USE_KUBO_JS=1 aegir test", + "test:node": "aegir test -t node && cross-env USE_KUBO_JS=1 aegir test -t node", + "test:chrome": "aegir test -t browser && cross-env USE_KUBO_JS=1 aegir test -t browser", + "test:firefox": "aegir test -t browser -- --browser firefox && cross-env USE_KUBO_JS=1 aegir test -t browser -- --browser firefox", "release": "aegir release" }, "dependencies": { @@ -165,6 +165,7 @@ "devDependencies": { "@types/hapi__hapi": "^20.0.9", "aegir": "^37.0.15", + "cross-env": "^7.0.3", "go-ipfs": "^0.13.0", "ipfs": "^0.63.5", "ipfs-client": "^0.8.3", From ee3b36a2a077adc0db90783d7b117a7316ec09fa Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Mon, 19 Sep 2022 11:51:50 -0700 Subject: [PATCH 04/23] fix: Remove USE_KUBO_JS env var --- .aegir.js | 11 ++++------- package.json | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.aegir.js b/.aegir.js index c133ab01..aba6a414 100644 --- a/.aegir.js +++ b/.aegir.js @@ -28,17 +28,14 @@ export default { ipfsModule, } - if (process.env.USE_KUBO_JS) { - controllerOptions.kuboRpcModule = kuboRpcModule - } else { - controllerOptions.ipfsHttpModule = ipfsHttpModule - } const server = createServer(undefined, controllerOptions, { go: { - ipfsBin: goIpfsModule.path() + ipfsBin: goIpfsModule.path(), + kuboRpcModule }, js: { - ipfsBin: ipfsModule.path() + ipfsBin: ipfsModule.path(), + ipfsHttpModule } } ) diff --git a/package.json b/package.json index e9532df3..71f0fa08 100644 --- a/package.json +++ b/package.json @@ -144,10 +144,10 @@ "scripts": { "lint": "aegir lint", "build": "aegir build", - "test": "aegir test && cross-env USE_KUBO_JS=1 aegir test", - "test:node": "aegir test -t node && cross-env USE_KUBO_JS=1 aegir test -t node", - "test:chrome": "aegir test -t browser && cross-env USE_KUBO_JS=1 aegir test -t browser", - "test:firefox": "aegir test -t browser -- --browser firefox && cross-env USE_KUBO_JS=1 aegir test -t browser -- --browser firefox", + "test": "aegir test", + "test:node": "aegir test -t node", + "test:chrome": "aegir test -t browser", + "test:firefox": "aegir test -t browser -- --browser firefox", "release": "aegir release" }, "dependencies": { From 60c2c8440fded01d525c74b32c941109058f851d Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:46:02 -0700 Subject: [PATCH 05/23] chore(deps): remove cross-env --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 71f0fa08..3d3cd67f 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,6 @@ "devDependencies": { "@types/hapi__hapi": "^20.0.9", "aegir": "^37.0.15", - "cross-env": "^7.0.3", "go-ipfs": "^0.13.0", "ipfs": "^0.63.5", "ipfs-client": "^0.8.3", From 004ebc66cb2008fceb53b5dc42efe30d4a7b3ae8 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 21 Sep 2022 09:30:25 -0700 Subject: [PATCH 06/23] docs(readme): update npm install cmds for using kubo-rpc-client --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f65a5107..40600e34 100644 --- a/README.md +++ b/README.md @@ -62,17 +62,15 @@ $ npm i ipfsd-ctl Version 1.0.0 changed a bit the api and the options methods take so please read the documentation below. -Please ensure your project also has dependencies on `ipfs`, `ipfs-http-client` and `go-ipfs`. +Please ensure your project also has dependencies on `ipfs`, `ipfs-http-client`, `kubo-rpc-module`, and `go-ipfs`. ```sh -npm install --save ipfs -npm install --save ipfs-http-client -npm install --save go-ipfs +npm install --save ipfs ipfs-http-client go-ipfs kubo-rpc-client ``` -If you are only going to use the `go` implementation of IPFS, you can skip installing the `js` implementation and vice versa, though both will require the `ipfs-http-client` module. +If you are only going to use the `go` implementation of IPFS, you can skip installing the `js` implementation and `ipfs-http-client` module. (e.g. `npm i --save go-ipfs kubo-rpc-client`) -If you are only using the `proc` type in-process IPFS node, you can skip installing `go-ipfs` and `ipfs-http-client`. +If you are only using the `proc` type in-process IPFS node, you can skip installing `go-ipfs` and `ipfs-http-client`. (e.g. `npm i --save ipfs`) > You also need to explicitly defined the options `ipfsBin`, `ipfsModule` and `ipfsHttpModule` according to your needs. Check [ControllerOptions](#controlleroptions) and [ControllerOptionsOverrides](#controlleroptionsoverrides) for more information. From b82834164ec9dd11b2bb7af6ef76213225000406 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:47:21 -0700 Subject: [PATCH 07/23] chore: log whether kubo-rpc-client or ipfs-http-client is in use --- src/ipfsd-client.js | 3 +++ src/ipfsd-daemon.js | 3 +++ src/ipfsd-in-proc.js | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/src/ipfsd-client.js b/src/ipfsd-client.js index 0e06b719..ad7e402a 100644 --- a/src/ipfsd-client.js +++ b/src/ipfsd-client.js @@ -9,6 +9,7 @@ const daemonLog = { info: logger('ipfsd-ctl:client:stdout'), err: logger('ipfsd-ctl:client:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:client:rpcModule:stdout') /** @typedef {import("./index").ControllerOptions} ControllerOptions */ @@ -96,8 +97,10 @@ class Client { }) } else if (this.apiAddr) { if (this.opts.kuboRpcModule != null) { + rpcModuleLogger('Using kubo-rpc-client') this.api = this.opts.kuboRpcModule.create(this.apiAddr) } else if (this.opts.ipfsHttpModule != null) { + rpcModuleLogger('Using ipfs-http-client') this.api = this.opts.ipfsHttpModule.create(this.apiAddr) } else { throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.js index f9b3e84e..b8e4b824 100644 --- a/src/ipfsd-daemon.js +++ b/src/ipfsd-daemon.js @@ -15,6 +15,7 @@ const daemonLog = { info: logger('ipfsd-ctl:daemon:stdout'), err: logger('ipfsd-ctl:daemon:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:daemon:rpcModule:stdout') /** * @param {Error & { stdout: string, stderr: string }} err @@ -101,8 +102,10 @@ class Daemon { }) } else if (this.apiAddr) { if (this.opts.kuboRpcModule != null) { + rpcModuleLogger('Using kubo-rpc-client') this.api = this.opts.kuboRpcModule.create(this.apiAddr) } else if (this.opts.ipfsHttpModule != null) { + rpcModuleLogger('Using ipfs-http-client') this.api = this.opts.ipfsHttpModule.create(this.apiAddr) } else { throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.js index 8d21bf3a..13602ee2 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.js @@ -9,6 +9,8 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:client:rpcModule:stdout') + /** * @typedef {import("./types").ControllerOptions} ControllerOptions * @typedef {import("./types").InitOptions} InitOptions @@ -69,8 +71,10 @@ class InProc { this.apiAddr = new Multiaddr(addr) if (this.opts.kuboRpcModule != null) { + rpcModuleLogger('Using kubo-rpc-client') this.api = this.opts.kuboRpcModule.create(addr) } else if (this.opts.ipfsHttpModule != null) { + rpcModuleLogger('Using ipfs-http-client') this.api = this.opts.ipfsHttpModule.create(addr) } else { throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') From e4e8bb51ac508d85a93eca3b155dd151e1564357 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:03:55 -0700 Subject: [PATCH 08/23] chore(tests): use kubo-rpc-client for commmunication with go controllers --- test/controller.spec.js | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/test/controller.spec.js b/test/controller.spec.js index 2e3ba1d0..ec661cab 100644 --- a/test/controller.spec.js +++ b/test/controller.spec.js @@ -8,6 +8,7 @@ import { isBrowser, isWebWorker, isNode } from 'wherearewe' import waitFor from 'p-wait-for' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' +import * as kuboRpcModule from 'kubo-rpc-client' // @ts-ignore no types import * as goIpfsModule from 'go-ipfs' @@ -16,6 +17,9 @@ import * as goIpfsModule from 'go-ipfs' */ /** + * This array of ControllerOptions is looped over for each test to ensure we're + * testing each combination of options. + * * @type {ControllerOptions[]} */ const types = [{ @@ -26,6 +30,7 @@ const types = [{ } }, { type: 'go', + kuboRpcModule, ipfsOptions: { init: false, start: false @@ -45,6 +50,7 @@ const types = [{ } }, { type: 'go', + kuboRpcModule, remote: true, ipfsOptions: { init: false, @@ -52,18 +58,31 @@ const types = [{ } }] +/** + * + * @param {ControllerOptions} opts + * @param {ControllerOptions} additionalOpts + */ +function addCorrectRpcModule (opts, additionalOpts) { + if (opts.type === 'go') { + additionalOpts.kuboRpcModule = kuboRpcModule + } else { + additionalOpts.ipfsHttpModule = ipfsHttpModule + } +} describe('Controller API', async function () { this.timeout(60000) const factory = createFactory({ test: true, - ipfsHttpModule, ipfsModule: (await import('ipfs')) }, { js: { + ipfsHttpModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, go: { + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined } }) @@ -192,13 +211,12 @@ describe('Controller API', async function () { // have to use createController so we don't try to shut down // the node twice during test cleanup const ctl = await createController(merge( - opts, { - ipfsHttpModule, + opts, addCorrectRpcModule(opts, { ipfsModule, ipfsOptions: { repo: factory.controllers[0].path } - } + }) )) await ctl.init() @@ -220,7 +238,7 @@ describe('Controller API', async function () { const ctl1 = await createController(merge( { type: 'go', - ipfsHttpModule, + kuboRpcModule, ipfsBin: goIpfsModule.path(), test: true, disposable: true, @@ -233,7 +251,7 @@ describe('Controller API', async function () { expect(ctl1.started).to.be.true() const ctl2 = await createController(merge( - opts, { + opts, addCorrectRpcModule(opts, { ipfsHttpModule, ipfsModule, test: true, @@ -242,7 +260,7 @@ describe('Controller API', async function () { repo: ctl1.path, start: true } - } + }) )) expect(ctl2.started).to.be.true() From ab88fed155fc8857117dec98a03782731e5901d0 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:39:07 -0700 Subject: [PATCH 09/23] chore(lint): fix lint failure --- src/ipfsd-in-proc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.js index ff8d9772..5dc1f452 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.js @@ -80,7 +80,7 @@ class InProc { } else { throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') } - + this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } From 0ed3917a92350662b9689cecc28009ae057d60ab Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 21 Sep 2022 12:11:16 -0700 Subject: [PATCH 10/23] fix: addCorrectRpcModule returns the additionalOpts object --- test/controller.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/controller.spec.js b/test/controller.spec.js index fe7008b0..309401fb 100644 --- a/test/controller.spec.js +++ b/test/controller.spec.js @@ -69,6 +69,8 @@ function addCorrectRpcModule (opts, additionalOpts) { } else { additionalOpts.ipfsHttpModule = ipfsHttpModule } + + return additionalOpts } describe('Controller API', async function () { this.timeout(60000) From 9eb0b51d38fbdf0354be6fc858f1efc224a762dc Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 22 Sep 2022 09:07:34 -0700 Subject: [PATCH 11/23] docs(readme): correct kubo-rpc-client name Co-authored-by: Alex Potsides --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40600e34..a4187888 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ $ npm i ipfsd-ctl Version 1.0.0 changed a bit the api and the options methods take so please read the documentation below. -Please ensure your project also has dependencies on `ipfs`, `ipfs-http-client`, `kubo-rpc-module`, and `go-ipfs`. +Please ensure your project also has dependencies on `ipfs`, `ipfs-http-client`, `kubo-rpc-client`, and `go-ipfs`. ```sh npm install --save ipfs ipfs-http-client go-ipfs kubo-rpc-client From 8de4a2db61af80cc9b1f0c0e50f9293160cc3a94 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 22 Sep 2022 09:14:48 -0700 Subject: [PATCH 12/23] fix: use correct logger namespace convention --- src/ipfsd-client.js | 2 +- src/ipfsd-daemon.js | 2 +- src/ipfsd-in-proc.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ipfsd-client.js b/src/ipfsd-client.js index 2947d3d9..a8dec87e 100644 --- a/src/ipfsd-client.js +++ b/src/ipfsd-client.js @@ -9,7 +9,7 @@ const daemonLog = { info: logger('ipfsd-ctl:client:stdout'), err: logger('ipfsd-ctl:client:stderr') } -const rpcModuleLogger = logger('ipfsd-ctl:client:rpcModule:stdout') +const rpcModuleLogger = logger('ipfsd-ctl:client') /** * @typedef {import('./index').ControllerOptions} ControllerOptions diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.js index 34323df9..07e4290b 100644 --- a/src/ipfsd-daemon.js +++ b/src/ipfsd-daemon.js @@ -19,7 +19,7 @@ const daemonLog = { info: logger('ipfsd-ctl:daemon:stdout'), err: logger('ipfsd-ctl:daemon:stderr') } -const rpcModuleLogger = logger('ipfsd-ctl:daemon:rpcModule:stdout') +const rpcModuleLogger = logger('ipfsd-ctl:daemon') /** * @param {Error & { stdout: string, stderr: string }} err diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.js index 5dc1f452..e54ddae6 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.js @@ -9,7 +9,7 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } -const rpcModuleLogger = logger('ipfsd-ctl:client:rpcModule:stdout') +const rpcModuleLogger = logger('ipfsd-ctl:client') /** * @typedef {import('./types').ControllerOptions} ControllerOptions From 68673b8229421f9518eaac3bf5783d4bb5f5d51e Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 22 Sep 2022 09:27:40 -0700 Subject: [PATCH 13/23] fix: move kubo-rpc-client to devDeps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bd79190b..500312a7 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,6 @@ "execa": "^6.1.0", "ipfs-utils": "^9.0.1", "joi": "^17.2.1", - "kubo-rpc-client": "^1.0.0", "merge-options": "^3.0.1", "nanoid": "^4.0.0", "p-wait-for": "^5.0.0", @@ -174,6 +173,7 @@ "ipfs-client": "^0.9.0", "ipfs-core-types": "^0.12.0", "ipfs-http-client": "^58.0.0", + "kubo-rpc-client": "^1.0.0", "util": "^0.12.4" }, "browser": { From 48adc6376a054019dc09df5065152453e38f99d3 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 22 Sep 2022 09:28:18 -0700 Subject: [PATCH 14/23] fix: support generics for Controller type --- src/types.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/types.ts b/src/types.ts index 69b12bd8..8020c49c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,20 +14,20 @@ export interface PeerData { addresses: Multiaddr[] } -export interface Controller { - init: (options?: InitOptions) => Promise - start: () => Promise - stop: () => Promise - cleanup: () => Promise +export interface Controller { + init: (options?: InitOptions) => Promise> + start: () => Promise> + stop: () => Promise> + cleanup: () => Promise> pid: () => Promise version: () => Promise path: string started: boolean initialized: boolean clean: boolean - api: IPFS + api: Type extends 'go' ? import('kubo-rpc-client').IPFSHTTPClient : IPFS subprocess?: Subprocess | null - opts: ControllerOptions + opts: ControllerOptions apiAddr: Multiaddr peer: PeerData } @@ -135,7 +135,7 @@ export interface IPFSOptions { repoAutoMigrate?: boolean } -export interface ControllerOptions { +export interface ControllerOptions { /** * Flag to activate custom config for tests */ @@ -155,7 +155,7 @@ export interface ControllerOptions { /** * The daemon type */ - type?: NodeType + type?: Type /** * Additional environment variables, passed to executing shell. Only applies for Daemon controllers */ @@ -199,15 +199,15 @@ export interface ControllerOptions { } export interface ControllerOptionsOverrides { - js?: ControllerOptions - go?: ControllerOptions - proc?: ControllerOptions + js?: ControllerOptions<'js'> + go?: ControllerOptions<'go'> + proc?: ControllerOptions<'proc'> } -export interface Factory { +export interface Factory { tmpDir: (options?: ControllerOptions) => Promise - spawn: (options?: ControllerOptions) => Promise + spawn: (options?: ControllerOptions) => Promise> clean: () => Promise - controllers: Controller[] - opts: ControllerOptions + controllers: Array> + opts: ControllerOptions } From cb4c37395797e0471b879e6cb4e1b3ce85693d13 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Fri, 23 Sep 2022 11:59:51 -0700 Subject: [PATCH 15/23] chore(deps): use kubo-rpc-client@1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 500312a7..7225ad62 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,7 @@ "ipfs-client": "^0.9.0", "ipfs-core-types": "^0.12.0", "ipfs-http-client": "^58.0.0", - "kubo-rpc-client": "^1.0.0", + "kubo-rpc-client": "^1.0.1", "util": "^0.12.4" }, "browser": { From 5783286413aaa615212ad133f362cd136999543d Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 29 Sep 2022 16:32:03 +0100 Subject: [PATCH 16/23] feat!: convert to typescript Converts this module to typescript --- .aegir.js | 3 +- README.md | 5 ++- package.json | 25 +++---------- src/{config.js => config.ts} | 0 src/endpoint/{routes.js => routes.ts} | 35 +++++++------------ .../{server.browser.js => server.browser.ts} | 0 src/endpoint/{server.js => server.ts} | 0 src/{factory.js => factory.ts} | 0 src/{index.js => index.ts} | 0 src/{ipfsd-client.js => ipfsd-client.ts} | 0 src/{ipfsd-daemon.js => ipfsd-daemon.ts} | 0 src/{ipfsd-in-proc.js => ipfsd-in-proc.ts} | 0 src/{utils.browser.js => utils.browser.ts} | 0 src/{utils.js => utils.ts} | 4 +-- test/{browser.js => browser.ts} | 0 test/{browser.utils.js => browser.utils.ts} | 0 ...{controller.spec.js => controller.spec.ts} | 0 test/{create.spec.js => create.spec.ts} | 0 test/{factory.spec.js => factory.spec.ts} | 0 test/{node.routes.js => node.routes.ts} | 0 test/{node.js => node.ts} | 0 test/{node.utils.js => node.utils.ts} | 0 22 files changed, 23 insertions(+), 49 deletions(-) rename src/{config.js => config.ts} (100%) rename src/endpoint/{routes.js => routes.ts} (85%) rename src/endpoint/{server.browser.js => server.browser.ts} (100%) rename src/endpoint/{server.js => server.ts} (100%) rename src/{factory.js => factory.ts} (100%) rename src/{index.js => index.ts} (100%) rename src/{ipfsd-client.js => ipfsd-client.ts} (100%) rename src/{ipfsd-daemon.js => ipfsd-daemon.ts} (100%) rename src/{ipfsd-in-proc.js => ipfsd-in-proc.ts} (100%) rename src/{utils.browser.js => utils.browser.ts} (100%) rename src/{utils.js => utils.ts} (97%) rename test/{browser.js => browser.ts} (100%) rename test/{browser.utils.js => browser.utils.ts} (100%) rename test/{controller.spec.js => controller.spec.ts} (100%) rename test/{create.spec.js => create.spec.ts} (100%) rename test/{factory.spec.js => factory.spec.ts} (100%) rename test/{node.routes.js => node.routes.ts} (100%) rename test/{node.js => node.ts} (100%) rename test/{node.utils.js => node.utils.ts} (100%) diff --git a/.aegir.js b/.aegir.js index 8b6ca72d..42d7cda0 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,4 +1,3 @@ -import { createServer } from './src/index.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' import * as goIpfsModule from 'go-ipfs' @@ -20,6 +19,8 @@ export default { } }, before: async () => { + const { createServer } = await import('./dist/src/index.js') + const server = createServer(undefined, { ipfsModule, ipfsHttpModule diff --git a/README.md b/README.md index f65a5107..ba5bc188 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # ipfsd-ctl -[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io) -[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -[![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs) +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) [![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfsd-ctl.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfsd-ctl) [![CI](https://img.shields.io/github/workflow/status/ipfs/js-ipfsd-ctl/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/ipfs/js-ipfsd-ctl/actions/workflows/js-test-and-release.yml) diff --git a/package.json b/package.json index fde7a455..8357bc75 100644 --- a/package.json +++ b/package.json @@ -22,25 +22,9 @@ }, "type": "module", "types": "./dist/src/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ], - "src/*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ] - } - }, "files": [ "src", - "dist", + "dist/src", "!dist/test", "!**/*.tsbuildinfo" ], @@ -142,6 +126,7 @@ ] }, "scripts": { + "clean": "aegir clean", "lint": "aegir lint", "build": "aegir build", "test": "aegir test", @@ -176,9 +161,9 @@ "util": "^0.12.4" }, "browser": { - "./src/endpoint/server.js": "./src/endpoint/server.browser.js", - "./src/utils.js": "./src/utils.browser.js", - "./src/ipfsd-daemon.js": "./src/ipfsd-client.js", + "./dist/src/endpoint/server.js": "./dist/src/endpoint/server.browser.js", + "./dist/src/utils.js": "./dist/src/utils.browser.js", + "./dist/src/ipfsd-daemon.js": "./dist/src/ipfsd-client.js", "go-ipfs": false }, "jsdelivr": "dist/index.min.js", diff --git a/src/config.js b/src/config.ts similarity index 100% rename from src/config.js rename to src/config.ts diff --git a/src/endpoint/routes.js b/src/endpoint/routes.ts similarity index 85% rename from src/endpoint/routes.js rename to src/endpoint/routes.ts index 3f488805..e84b1f41 100644 --- a/src/endpoint/routes.js +++ b/src/endpoint/routes.ts @@ -3,6 +3,8 @@ import Joi from 'joi' import boom from '@hapi/boom' import { logger } from '@libp2p/logger' import { tmpDir } from '../utils.js' +import type { Server } from '@hapi/hapi' +import type { Factory } from '../types.js' /** * @typedef {import('../types').Factory} Factory @@ -18,10 +20,7 @@ const routeOptions = { } } -/** - * @param {Error & { stdout?: string }} err - */ -const badRequest = err => { +const badRequest = (err: Error & { stdout?: string }) => { let msg if (err.stdout) { msg = err.stdout + ' - ' + err.message @@ -32,19 +31,9 @@ const badRequest = err => { throw boom.badRequest(msg) } -/** - * @type {Record} - */ -const nodes = {} +const nodes: Record = {} -/** - * @namespace EndpointServerRoutes - * @ignore - * @param {import('@hapi/hapi').Server} server - * @param {() => Factory | Promise} createFactory - * @returns {void} - */ -export default (server, createFactory) => { +export default (server: Server, createFactory: () => Factory | Promise): void => { server.route({ method: 'GET', path: '/util/tmp-dir', @@ -52,7 +41,7 @@ export default (server, createFactory) => { const type = request.query.type || 'go' try { return { tmpDir: await tmpDir(type) } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } } @@ -66,7 +55,7 @@ export default (server, createFactory) => { try { return { version: await nodes[id].version() } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -95,7 +84,7 @@ export default (server, createFactory) => { path: nodes[id].path, clean: nodes[id].clean } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } } @@ -117,7 +106,7 @@ export default (server, createFactory) => { return { initialized: nodes[id].initialized } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -141,7 +130,7 @@ export default (server, createFactory) => { gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : '', grpcAddr: nodes[id].grpcAddr ? nodes[id].grpcAddr.toString() : '' } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -163,7 +152,7 @@ export default (server, createFactory) => { await nodes[id].cleanup() return h.response().code(200) - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -183,7 +172,7 @@ export default (server, createFactory) => { await nodes[id].stop() return h.response().code(200) - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, diff --git a/src/endpoint/server.browser.js b/src/endpoint/server.browser.ts similarity index 100% rename from src/endpoint/server.browser.js rename to src/endpoint/server.browser.ts diff --git a/src/endpoint/server.js b/src/endpoint/server.ts similarity index 100% rename from src/endpoint/server.js rename to src/endpoint/server.ts diff --git a/src/factory.js b/src/factory.ts similarity index 100% rename from src/factory.js rename to src/factory.ts diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/ipfsd-client.js b/src/ipfsd-client.ts similarity index 100% rename from src/ipfsd-client.js rename to src/ipfsd-client.ts diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.ts similarity index 100% rename from src/ipfsd-daemon.js rename to src/ipfsd-daemon.ts diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.ts similarity index 100% rename from src/ipfsd-in-proc.js rename to src/ipfsd-in-proc.ts diff --git a/src/utils.browser.js b/src/utils.browser.ts similarity index 100% rename from src/utils.browser.js rename to src/utils.browser.ts diff --git a/src/utils.js b/src/utils.ts similarity index 97% rename from src/utils.js rename to src/utils.ts index 2688f037..f088da00 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -15,7 +15,7 @@ export const removeRepo = async (repoPath) => { await fs.promises.rm(repoPath, { recursive: true }) - } catch (/** @type {any} */ err) { + } catch (err: any) { // ignore } } @@ -47,7 +47,7 @@ export const checkForRunningApi = (repoPath = '') => { let api try { api = fs.readFileSync(path.join(repoPath, 'api')) - } catch (/** @type {any} */ err) { + } catch (err: any) { log('Unable to open api file') } diff --git a/test/browser.js b/test/browser.ts similarity index 100% rename from test/browser.js rename to test/browser.ts diff --git a/test/browser.utils.js b/test/browser.utils.ts similarity index 100% rename from test/browser.utils.js rename to test/browser.utils.ts diff --git a/test/controller.spec.js b/test/controller.spec.ts similarity index 100% rename from test/controller.spec.js rename to test/controller.spec.ts diff --git a/test/create.spec.js b/test/create.spec.ts similarity index 100% rename from test/create.spec.js rename to test/create.spec.ts diff --git a/test/factory.spec.js b/test/factory.spec.ts similarity index 100% rename from test/factory.spec.js rename to test/factory.spec.ts diff --git a/test/node.routes.js b/test/node.routes.ts similarity index 100% rename from test/node.routes.js rename to test/node.routes.ts diff --git a/test/node.js b/test/node.ts similarity index 100% rename from test/node.js rename to test/node.ts diff --git a/test/node.utils.js b/test/node.utils.ts similarity index 100% rename from test/node.utils.js rename to test/node.utils.ts From 2f98a0b269beda3a046fc1b04f85885d17752f24 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 30 Sep 2022 08:16:53 +0100 Subject: [PATCH 17/23] chore: fix linting --- .aegir.js | 14 +- package.json | 5 +- src/config.ts | 15 +- src/endpoint/routes.ts | 26 ++-- src/endpoint/server.browser.ts | 18 +-- src/endpoint/server.ts | 44 +++--- src/factory.ts | 83 +++++----- src/index.ts | 266 +++++++++++++++++++++++++++++++-- src/ipfsd-client.ts | 135 ++++++----------- src/ipfsd-daemon.ts | 186 ++++++++++------------- src/ipfsd-in-proc.ts | 102 +++++-------- src/types.ts | 209 -------------------------- src/utils.browser.ts | 22 +-- src/utils.ts | 69 ++++----- test/controller.spec.ts | 53 ++++--- test/create.spec.ts | 34 ++--- test/factory.spec.ts | 14 +- test/node.routes.ts | 12 +- test/node.ts | 5 +- tsconfig.json | 3 +- 20 files changed, 575 insertions(+), 740 deletions(-) delete mode 100644 src/types.ts diff --git a/.aegir.js b/.aegir.js index 42d7cda0..f94961d0 100644 --- a/.aegir.js +++ b/.aegir.js @@ -3,21 +3,11 @@ import * as ipfsHttpModule from 'ipfs-http-client' import * as goIpfsModule from 'go-ipfs' /** @type {import('aegir').Options["build"]["config"]} */ -/* -const esbuild = { - inject: [path.join(__dirname, 'scripts/node-globals.js')], -} -*/ -export default { +const config = { bundlesize: { maxSize: '35kB' }, test: { - browser: { - config: { - //buildConfig: esbuild - } - }, before: async () => { const { createServer } = await import('./dist/src/index.js') @@ -48,3 +38,5 @@ export default { } } } + +export default config diff --git a/package.json b/package.json index 8357bc75..6ea9038b 100644 --- a/package.json +++ b/package.json @@ -128,10 +128,11 @@ "scripts": { "clean": "aegir clean", "lint": "aegir lint", + "dep-check": "aegir dep-check", "build": "aegir build", "test": "aegir test", - "test:node": "aegir test -t node", - "test:chrome": "aegir test -t browser", + "test:node": "aegir test -t node --cov", + "test:chrome": "aegir test -t browser --cov", "test:firefox": "aegir test -t browser -- --browser firefox", "release": "aegir release" }, diff --git a/src/config.ts b/src/config.ts index 808cbb45..52bd4104 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,12 +1,13 @@ import { isBrowser, isWebWorker } from 'wherearewe' +import type { NodeType } from './index.js' -/** - * @param {object} args - * @param {import('./types').NodeType} args.type - */ -export default ({ type }) => { - /** @type {string[]} */ - let swarm +export interface ConfigInit { + type?: NodeType +} + +export default (init: ConfigInit) => { + const { type } = init + let swarm: string[] // from the browser tell remote nodes to listen over WS if (type !== 'proc' && (isBrowser || isWebWorker)) { diff --git a/src/endpoint/routes.ts b/src/endpoint/routes.ts index e84b1f41..ffa608fe 100644 --- a/src/endpoint/routes.ts +++ b/src/endpoint/routes.ts @@ -4,11 +4,7 @@ import boom from '@hapi/boom' import { logger } from '@libp2p/logger' import { tmpDir } from '../utils.js' import type { Server } from '@hapi/hapi' -import type { Factory } from '../types.js' - -/** - * @typedef {import('../types').Factory} Factory - */ +import type { Factory } from '../index.js' const debug = logger('ipfsd-ctl:routes') @@ -22,7 +18,7 @@ const routeOptions = { const badRequest = (err: Error & { stdout?: string }) => { let msg - if (err.stdout) { + if (err.stdout != null) { msg = err.stdout + ' - ' + err.message } else { msg = err.message @@ -38,7 +34,7 @@ export default (server: Server, createFactory: () => Factory | Promise) method: 'GET', path: '/util/tmp-dir', handler: async (request) => { - const type = request.query.type || 'go' + const type = request.query.type ?? 'go' try { return { tmpDir: await tmpDir(type) } } catch (err: any) { @@ -66,7 +62,7 @@ export default (server: Server, createFactory: () => Factory | Promise) method: 'POST', path: '/spawn', handler: async (request) => { - const opts = request.payload || {} + const opts = request.payload ?? {} try { const ipfsd = await createFactory() const id = nanoid() @@ -74,9 +70,9 @@ export default (server: Server, createFactory: () => Factory | Promise) nodes[id] = await ipfsd.spawn(opts) return { id: id, - apiAddr: nodes[id].apiAddr ? nodes[id].apiAddr.toString() : '', - gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : '', - grpcAddr: nodes[id].grpcAddr ? nodes[id].grpcAddr.toString() : '', + apiAddr: nodes[id].apiAddr?.toString(), + gatewayAddr: nodes[id].gatewayAddr?.toString(), + grpcAddr: nodes[id].grpcAddr?.toString(), initialized: nodes[id].initialized, started: nodes[id].started, disposable: nodes[id].disposable, @@ -98,7 +94,7 @@ export default (server: Server, createFactory: () => Factory | Promise) path: '/init', handler: async (request) => { const id = request.query.id - const payload = request.payload || {} + const payload = request.payload ?? {} try { await nodes[id].init(payload) @@ -126,9 +122,9 @@ export default (server: Server, createFactory: () => Factory | Promise) await nodes[id].start() return { - apiAddr: nodes[id].apiAddr ? nodes[id].apiAddr.toString() : '', - gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : '', - grpcAddr: nodes[id].grpcAddr ? nodes[id].grpcAddr.toString() : '' + apiAddr: nodes[id].apiAddr?.toString(), + gatewayAddr: nodes[id].gatewayAddr?.toString(), + grpcAddr: nodes[id].grpcAddr?.toString() } } catch (err: any) { badRequest(err) diff --git a/src/endpoint/server.browser.ts b/src/endpoint/server.browser.ts index ec4f9986..7f318495 100644 --- a/src/endpoint/server.browser.ts +++ b/src/endpoint/server.browser.ts @@ -1,24 +1,10 @@ /* eslint-disable no-console */ /** - * Creates an instance of Server. - * - * @class + * Creates an instance of Server */ class Server { - /** - * @class - * @param {object} options - * @param {number} [options.port=43134] - Server port. - * @param {Function} createNode - */ - constructor (options, createNode) { - options = options || { port: 43134 } - - /** @type {*} */ - this.server = null - this.port = options.port - this.createNode = createNode + constructor () { console.warn('Server not implemented in the browser') } diff --git a/src/endpoint/server.ts b/src/endpoint/server.ts index ae61cd14..af406400 100644 --- a/src/endpoint/server.ts +++ b/src/endpoint/server.ts @@ -1,38 +1,34 @@ import Hapi from '@hapi/hapi' +import type { CreateFactory } from '../index.js' import routes from './routes.js' -/** - * @typedef {import('../types').Factory} Factory - */ +export interface ServerInit { + port?: number + host?: string +} /** - * Creates an instance of Server. - * - * @class + * Creates an instance of Server */ class Server { - /** - * @class - * @param {object} options - * @param {number} [options.port=43134] - * @param {string} [options.host='localhost'] - * @param {() => Factory | Promise} createFactory - */ - constructor (options = { port: 43134, host: 'localhost' }, createFactory) { + private readonly options: ServerInit + private server: Hapi.Server | null + public port: number + public host: string + private readonly createFactory: CreateFactory + + constructor (options: ServerInit = { port: 43134, host: 'localhost' }, createFactory: CreateFactory) { this.options = options this.server = null - this.port = this.options.port == null ? 43134 : this.options.port - this.host = this.options.host == null ? 'localhost' : this.options.host + this.port = this.options.port ?? 43134 + this.host = this.options.host ?? 'localhost' this.createFactory = createFactory } /** * Start the server - * - * @param {number} port - * @returns {Promise} */ - async start (port = this.port) { + async start (port = this.port): Promise { this.port = port this.server = new Hapi.Server({ port: port, @@ -51,13 +47,9 @@ class Server { /** * Stop the server - * - * @param {object} [options] - * @param {number} options.timeout - * @returns {Promise} */ - async stop (options) { - if (this.server) { + async stop (options: { timeout: number }): Promise { + if (this.server != null) { await this.server.stop(options) } } diff --git a/src/factory.ts b/src/factory.ts index 198cd957..1621efb7 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -6,19 +6,13 @@ import ControllerDaemon from './ipfsd-daemon.js' import ControllerRemote from './ipfsd-client.js' import ControllerProc from './ipfsd-in-proc.js' import testsConfig from './config.js' +import type { Controller, ControllerOptions, ControllerOptionsOverrides, Factory } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) -/** - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').ControllerOptionsOverrides} ControllerOptionsOverrides - * @typedef {import('./types').IPFSOptions} IPFSOptions - * @typedef {import('./types').Controller} Controller - */ - const defaults = { remote: !isNode && !isElectronMain, - endpoint: process.env.IPFSD_CTL_SERVER || 'http://localhost:43134', + endpoint: process.env.IPFSD_CTL_SERVER ?? 'http://localhost:43134', disposable: true, test: false, type: 'go', @@ -29,27 +23,33 @@ const defaults = { forceKillTimeout: 5000 } +export interface ControllerOptionsOverridesWithEndpoint { + js?: ControllerOptionsWithEndpoint + go?: ControllerOptionsWithEndpoint + proc?: ControllerOptionsWithEndpoint +} + +export interface ControllerOptionsWithEndpoint extends ControllerOptions { + endpoint: string +} + /** * Factory class to spawn ipfsd controllers */ -class Factory { - /** - * - * @param {ControllerOptions} options - * @param {ControllerOptionsOverrides} overrides - Pre-defined overrides per controller type - */ - constructor (options = {}, overrides = {}) { - /** @type ControllerOptions */ - this.opts = merge(defaults, options) +class DefaultFactory implements Factory { + public opts: ControllerOptionsWithEndpoint + public controllers: Controller[] + + private readonly overrides: ControllerOptionsOverridesWithEndpoint - /** @type ControllerOptionsOverrides */ + constructor (options: ControllerOptions = {}, overrides: ControllerOptionsOverrides = {}) { + this.opts = merge(defaults, options) this.overrides = merge({ js: merge(this.opts, { type: 'js' }), go: merge(this.opts, { type: 'go' }), proc: merge(this.opts, { type: 'proc' }) }, overrides) - /** @type {Controller[]} */ this.controllers = [] } @@ -57,30 +57,24 @@ class Factory { * Utility method to get a temporary directory * useful in browsers to be able to generate temp * repos manually - * - * @param {ControllerOptions} [options] - * @returns {Promise} */ - async tmpDir (options = {}) { - const opts = merge(this.opts, options) + async tmpDir (options: ControllerOptions = {}): Promise { + const opts: ControllerOptions = merge(this.opts, options) - if (opts.remote) { + if (opts.remote === true) { const res = await http.get( - `${opts.endpoint}/util/tmp-dir`, - { searchParams: new URLSearchParams({ type: `${opts.type}` }) } + `${opts.endpoint ?? ''}/util/tmp-dir`, + { searchParams: new URLSearchParams({ type: opts.type ?? '' }) } ) const out = await res.json() return out.tmpDir } - return Promise.resolve(tmpDir(opts.type)) + return await Promise.resolve(tmpDir(opts.type)) } - /** - * @param {IPFSOptions & { endpoint: string }} options - */ - async _spawnRemote (options) { + async _spawnRemote (options: ControllerOptionsWithEndpoint) { const opts = { json: { ...options, @@ -105,13 +99,10 @@ class Factory { /** * Spawn an IPFSd Controller - * - * @param {ControllerOptions} options - * @returns {Promise} */ - async spawn (options = { }) { - const type = options.type || this.opts.type || 'go' - const opts = merge( + async spawn (options: ControllerOptions = { }): Promise { + const type = options.type ?? this.opts.type ?? 'go' + const opts: ControllerOptionsWithEndpoint = merge( this.overrides[type], options ) @@ -122,7 +113,7 @@ class Factory { start: false, init: false }, - opts.test + opts.test === true ? { config: testsConfig(opts), preload: { enabled: false } @@ -131,11 +122,11 @@ class Factory { opts.ipfsOptions ) - let ctl + let ctl: Controller if (opts.type === 'proc') { // spawn in-proc controller ctl = new ControllerProc({ ...opts, ipfsOptions }) - } else if (opts.remote) { + } else if (opts.remote === true) { // spawn remote controller ctl = await this._spawnRemote({ ...opts, ipfsOptions }) } else { @@ -147,10 +138,10 @@ class Factory { this.controllers.push(ctl) // Auto init and start controller - if (opts.disposable && (!options.ipfsOptions || (options.ipfsOptions && options.ipfsOptions.init !== false))) { + if (opts.disposable === true && (options.ipfsOptions == null || options.ipfsOptions?.init !== false)) { await ctl.init(ipfsOptions.init) } - if (opts.disposable && (!options.ipfsOptions || (options.ipfsOptions && options.ipfsOptions.start !== false))) { + if (opts.disposable === true && (options.ipfsOptions == null || options.ipfsOptions?.start !== false)) { await ctl.start() } @@ -160,10 +151,10 @@ class Factory { /** * Stop all controllers */ - async clean () { - await Promise.all(this.controllers.map(n => n.stop())) + async clean (): Promise { + await Promise.all(this.controllers.map(async n => await n.stop())) this.controllers = [] } } -export default Factory +export default DefaultFactory diff --git a/src/index.ts b/src/index.ts index e26ba641..9ea81245 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,235 @@ import DefaultFactory from './factory.js' import Server from './endpoint/server.js' +import type { IPFS } from 'ipfs-core-types' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { PeerId } from '@libp2p/interface-peer-id' +import type { ExecaChildProcess } from 'execa' -/** - * @typedef {import('./types').Controller} Controller - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').ControllerOptionsOverrides} ControllerOptionsOverrides - * @typedef {import('./types').Factory} Factory - */ +export interface PeerData { + id: PeerId + addresses: Multiaddr[] +} + +export interface Controller { + /** + * Initialize a repo + */ + init: (options?: InitOptions) => Promise + + /** + * Start the daemon + */ + start: () => Promise + + /** + * Stop the daemon + */ + stop: () => Promise + + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + */ + cleanup: () => Promise + + /** + * Get the pid of the `ipfs daemon` process + */ + pid: () => Promise + + /** + * Get the version of ipfs + */ + version: () => Promise + path: string + started: boolean + initialized: boolean + clean: boolean + api: IPFSAPI + subprocess?: ExecaChildProcess | null + opts: ControllerOptions + apiAddr: Multiaddr + peer: PeerData +} + +export interface RemoteState { + id: string + path: string + initialized: boolean + started: boolean + disposable: boolean + clean: boolean + apiAddr: string + gatewayAddr: string + grpcAddr: string +} + +export type NodeType = 'js' | 'go' | 'proc' + +export interface InitOptions { + pass?: string + bits?: number + algorithm?: string + emptyRepo?: boolean + profiles?: string[] + allowNew?: boolean + privateKey?: string +} + +export interface PreloadOptions { + enabled?: boolean + addresses?: string[] +} + +export interface ExperimentalOptions { + sharding?: boolean + ipnsPubsub?: boolean +} + +export interface CircuitRelayHopOptions { + enabled: boolean + active: boolean +} + +export interface CircuitRelayOptions { + enabled: boolean + hop: CircuitRelayHopOptions +} + +export interface IPFSOptions { + /** + * 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. + */ + repo?: string | any + /** + * 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 + */ + init?: boolean | InitOptions + /** + * If false, do not automatically start the IPFS node. Instead, you’ll need to manually call node.start() yourself. + */ + start?: boolean + /** + * A passphrase to encrypt/decrypt your keys. + */ + pass?: string + /** + * Prevents all logging output from the IPFS node. + */ + silent?: boolean + /** + * Configure circuit relay. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsrelay + */ + relay?: any + /** + * 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 + */ + preload?: boolean | PreloadOptions + /** + * Enable and configure experimental features. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsexperimental + */ + EXPERIMENTAL?: ExperimentalOptions + /** + * 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 + */ + config?: any + /** + * 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 + */ + ipld?: any + /** + * 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 + */ + libp2p?: any + /** + * Configure the libp2p connection manager. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsconnectionmanager + */ + connectionManager?: any + /** + * Run the node offline + */ + offline?: boolean + /** + * Perform any required repo migrations + */ + repoAutoMigrate?: boolean +} + +export interface ControllerOptions { + /** + * Flag to activate custom config for tests + */ + test?: boolean + /** + * Use remote endpoint to spawn the controllers. Defaults to `true` when not in node + */ + remote?: boolean + /** + * Endpoint URL to manage remote Controllers. (Defaults: 'http://localhost:43134') + */ + endpoint?: string + /** + * A new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits + */ + disposable?: boolean + /** + * The daemon type + */ + type?: NodeType + /** + * Additional environment variables, passed to executing shell. Only applies for Daemon controllers + */ + env?: Record + /** + * Custom cli args + */ + args?: string[] + /** + * Reference to an ipfs-http-client module + */ + ipfsHttpModule?: any + /** + * Reference to an ipfs or ipfs-core module + */ + ipfsModule?: any + /** + * Reference to an ipfs-core module + */ + ipfsClientModule?: any + /** + * Path to a IPFS executable + */ + ipfsBin?: string + /** + * Options for the IPFS node + */ + ipfsOptions?: IPFSOptions + /** + * Whether to use SIGKILL to quit a daemon that does not stop after `.stop()` is called. (default true) + */ + forceKill?: boolean + /** + * How long to wait before force killing a daemon in ms. (default 5000) + */ + forceKillTimeout?: number +} + +export interface ControllerOptionsOverrides { + js?: ControllerOptions + go?: ControllerOptions + proc?: ControllerOptions +} + +export interface Factory { + tmpDir: (options?: ControllerOptions) => Promise + spawn: (options?: ControllerOptions) => Promise + clean: () => Promise + controllers: Controller[] + opts: ControllerOptions +} + +export interface CreateFactory { (): Factory | Promise } /** * Creates a factory @@ -15,19 +238,25 @@ import Server from './endpoint/server.js' * @param {ControllerOptionsOverrides} [overrides] * @returns {Factory} */ -export const createFactory = (options, overrides) => { +export const createFactory = (options?: ControllerOptions, overrides?: ControllerOptionsOverrides): Factory => { return new DefaultFactory(options, overrides) } /** * Creates a node - * - * @param {ControllerOptions} [options] - * @returns {Promise} */ -export const createController = (options) => { +export const createController = async (options?: ControllerOptions): Promise => { const f = new DefaultFactory() - return f.spawn(options) + return await f.spawn(options) +} + +export interface IPFSAPI extends IPFS { + apiHost?: string + apiPort?: number + gatewayHost?: string + gatewayPort?: number + grpcHost?: string + grpcPort?: number } /** @@ -37,12 +266,19 @@ export const createController = (options) => { * @param {ControllerOptions} [factoryOptions] * @param {ControllerOptionsOverrides} [factoryOverrides] */ -export const createServer = (options, factoryOptions = {}, factoryOverrides = {}) => { +export const createServer = (options?: number | { port: number }, factoryOptions: ControllerOptions = {}, factoryOverrides: ControllerOptionsOverrides = {}) => { + let port: number | undefined + if (typeof options === 'number') { - options = { port: options } + port = options + } else if (options != null) { + port = options.port } - return new Server(options, () => { + return new Server({ + port, + host: '127.0.0.1' + }, () => { return createFactory(factoryOptions, factoryOverrides) }) } diff --git a/src/ipfsd-client.ts b/src/ipfsd-client.ts index 438991f6..7cc878c2 100644 --- a/src/ipfsd-client.ts +++ b/src/ipfsd-client.ts @@ -1,7 +1,8 @@ -import { multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import http from 'ipfs-utils/src/http.js' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData, RemoteState } from '.' const merge = mergeOptions.bind({ ignoreUndefined: true }) @@ -10,24 +11,29 @@ const daemonLog = { err: logger('ipfsd-ctl:client:stderr') } -/** - * @typedef {import('./index').ControllerOptions} ControllerOptions - * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr - */ - /** * Controller for remote nodes - * - * @class */ -class Client { - /** - * @class - * @param {string} baseUrl - * @param {import('./types').RemoteState} remoteState - * @param {ControllerOptions} options - */ - constructor (baseUrl, remoteState, options) { +class Client implements Controller { + public path: string + // @ts-expect-error set during startup + public api: IPFSAPI + public subprocess: null + public opts: ControllerOptions + public initialized: boolean + public started: boolean + public clean: boolean + // @ts-expect-error set during startup + public apiAddr: Multiaddr + + private readonly baseUrl: string + private readonly id: string + private readonly disposable: boolean + private gatewayAddr?: Multiaddr + private grpcAddr?: Multiaddr + private _peerId: PeerData | null + + constructor (baseUrl: string, remoteState: RemoteState, options: ControllerOptions) { this.opts = options this.baseUrl = baseUrl this.id = remoteState.id @@ -36,17 +42,12 @@ class Client { this.started = remoteState.started this.disposable = remoteState.disposable this.clean = remoteState.clean - this.api = null - /** @type {import('./types').Subprocess | null} */ this.subprocess = null - /** @type {Multiaddr} */ - this.apiAddr // eslint-disable-line no-unused-expressions this._setApi(remoteState.apiAddr) this._setGateway(remoteState.gatewayAddr) this._setGrpc(remoteState.grpcAddr) this._createApi() - /** @type {import('./types').PeerData | null} */ this._peerId = null } @@ -58,88 +59,67 @@ class Client { return this._peerId } - /** - * @private - * @param {string} addr - */ - _setApi (addr) { - if (addr) { + private _setApi (addr: string): void { + if (addr != null) { this.apiAddr = multiaddr(addr) } } - /** - * @private - * @param {string} addr - */ - _setGateway (addr) { - if (addr) { + private _setGateway (addr: string): void { + if (addr != null) { this.gatewayAddr = multiaddr(addr) } } - /** - * @private - * @param {string} addr - */ - _setGrpc (addr) { - if (addr) { + private _setGrpc (addr: string): void { + if (addr != null) { this.grpcAddr = multiaddr(addr) } } - /** - * @private - */ - _createApi () { - if (this.opts.ipfsClientModule && this.grpcAddr && this.apiAddr) { + private _createApi (): void { + if (this.opts.ipfsClientModule != null && this.grpcAddr != null && this.apiAddr != null) { this.api = this.opts.ipfsClientModule.create({ grpc: this.grpcAddr, http: this.apiAddr }) - } else if (this.apiAddr) { + } else if (this.apiAddr != null) { this.api = this.opts.ipfsHttpModule.create(this.apiAddr) } - if (this.api) { - if (this.apiAddr) { + if (this.api != null) { + if (this.apiAddr != null) { this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } - if (this.gatewayAddr) { + if (this.gatewayAddr != null) { this.api.gatewayHost = this.gatewayAddr.nodeAddress().address this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } - if (this.grpcAddr) { + if (this.grpcAddr != null) { this.api.grpcHost = this.grpcAddr.nodeAddress().address this.api.grpcPort = this.grpcAddr.nodeAddress().port } } } - /** - * Initialize a repo. - * - * @param {import('./types').InitOptions} [initOptions] - * @returns {Promise} - */ - async init (initOptions = {}) { + async init (initOptions: InitOptions = {}): Promise { if (this.initialized) { return this } let ipfsOptions = {} - if (this.opts.ipfsOptions != null && this.opts.ipfsOptions.init != null && !(typeof this.opts.ipfsOptions.init === 'boolean')) { + if (this.opts.ipfsOptions?.init != null && !(typeof this.opts.ipfsOptions.init === 'boolean')) { ipfsOptions = this.opts.ipfsOptions.init } const opts = merge( { emptyRepo: false, - profiles: this.opts.test ? ['test'] : [] + profiles: this.opts.test === true ? ['test'] : [] }, ipfsOptions, typeof initOptions === 'boolean' ? {} : initOptions @@ -158,14 +138,7 @@ class Client { return this } - /** - * Delete the repo that was being used. - * If the node was marked as `disposable` this will be called - * automatically when the process is exited. - * - * @returns {Promise} - */ - async cleanup () { + async cleanup (): Promise { if (this.clean) { return this } @@ -178,12 +151,7 @@ class Client { return this } - /** - * Start the daemon. - * - * @returns {Promise} - */ - async start () { + async start (): Promise { if (!this.started) { const req = await http.post( `${this.baseUrl}/start`, @@ -199,6 +167,10 @@ class Client { this.started = true } + if (this.api == null) { + throw new Error('api was not set') + } + // Add `peerId` const id = await this.api.id() this._peerId = id @@ -206,10 +178,7 @@ class Client { return this } - /** - * Stop the daemon - */ - async stop () { + async stop (): Promise { if (!this.started) { return this } @@ -227,12 +196,7 @@ class Client { return this } - /** - * Get the pid of the `ipfs daemon` process. - * - * @returns {Promise} - */ - async pid () { + async pid (): Promise { const req = await http.get( `${this.baseUrl}/pid`, { searchParams: new URLSearchParams({ id: this.id }) } @@ -242,12 +206,7 @@ class Client { return res.pid } - /** - * Get the version of ipfs - * - * @returns {Promise} - */ - async version () { + async version (): Promise { const req = await http.get( `${this.baseUrl}/version`, { searchParams: new URLSearchParams({ id: this.id }) } diff --git a/src/ipfsd-daemon.ts b/src/ipfsd-daemon.ts index 835af609..915003b9 100644 --- a/src/ipfsd-daemon.ts +++ b/src/ipfsd-daemon.ts @@ -1,17 +1,14 @@ -import { multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import fs from 'fs/promises' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' -import { execa } from 'execa' +import { execa, ExecaChildProcess } from 'execa' import { nanoid } from 'nanoid' import path from 'path' import os from 'os' import { checkForRunningApi, repoExists, tmpDir, defaultRepo, buildInitArgs, buildStartArgs } from './utils.js' import waitFor from 'p-wait-for' - -/** - * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr - */ +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) @@ -20,48 +17,44 @@ const daemonLog = { err: logger('ipfsd-ctl:daemon:stderr') } -/** - * @param {Error & { stdout: string, stderr: string }} err - */ -function translateError (err) { +function translateError (err: Error & { stdout: string, stderr: string }) { // get the actual error message to be the err.message err.message = `${err.stdout} \n\n ${err.stderr} \n\n ${err.message} \n\n` return err } -/** - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').Controller} Controller - */ - /** * Controller for daemon nodes - * - * @class - * */ -class Daemon { - /** - * @class - * @param {Required} opts - */ - constructor (opts) { +class Daemon implements Controller { + public path: string + // @ts-expect-error set during startup + public api: IPFSAPI + public subprocess?: ExecaChildProcess + public opts: ControllerOptions + public initialized: boolean + public started: boolean + public clean: boolean + // @ts-expect-error set during startup + public apiAddr: Multiaddr + + private gatewayAddr?: Multiaddr + private grpcAddr?: Multiaddr + private readonly exec?: string + private readonly env: Record + private readonly disposable: boolean + private _peerId: PeerData | null + + constructor (opts: ControllerOptions) { this.opts = opts - this.path = this.opts.ipfsOptions.repo || (opts.disposable ? tmpDir(opts.type) : defaultRepo(opts.type)) + this.path = this.opts.ipfsOptions?.repo ?? (opts.disposable === true ? tmpDir(opts.type) : defaultRepo(opts.type)) this.exec = this.opts.ipfsBin this.env = merge({ IPFS_PATH: this.path }, this.opts.env) - this.disposable = this.opts.disposable - this.subprocess = null + this.disposable = Boolean(this.opts.disposable) this.initialized = false this.started = false this.clean = true - /** @type {Multiaddr} */ - this.apiAddr // eslint-disable-line no-unused-expressions - this.grpcAddr = null - this.gatewayAddr = null - this.api = null - /** @type {import('./types').PeerData | null} */ this._peerId = null } @@ -73,67 +66,49 @@ class Daemon { return this._peerId } - /** - * @private - * @param {string} addr - */ - _setApi (addr) { + private _setApi (addr: string): void { this.apiAddr = multiaddr(addr) } - /** - * @private - * @param {string} addr - */ - _setGrpc (addr) { + private _setGrpc (addr: string): void { this.grpcAddr = multiaddr(addr) } - /** - * @private - * @param {string} addr - */ - _setGateway (addr) { + private _setGateway (addr: string): void { this.gatewayAddr = multiaddr(addr) } _createApi () { - if (this.opts.ipfsClientModule && this.grpcAddr) { + if (this.opts.ipfsClientModule != null && this.grpcAddr != null) { this.api = this.opts.ipfsClientModule.create({ grpc: this.grpcAddr, http: this.apiAddr }) - } else if (this.apiAddr) { + } else if (this.apiAddr != null) { this.api = this.opts.ipfsHttpModule.create(this.apiAddr) } - if (!this.api) { - throw new Error(`Could not create API from http '${this.apiAddr}' and/or gRPC '${this.grpcAddr}'`) + if (this.api == null) { + throw new Error(`Could not create API from http '${this.apiAddr.toString()}' and/or gRPC '${this.grpcAddr?.toString() ?? 'undefined'}'`) } - if (this.apiAddr) { + if (this.apiAddr != null) { this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } - if (this.gatewayAddr) { + if (this.gatewayAddr != null) { this.api.gatewayHost = this.gatewayAddr.nodeAddress().address this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } - if (this.grpcAddr) { + if (this.grpcAddr != null) { this.api.grpcHost = this.grpcAddr.nodeAddress().address this.api.grpcPort = this.grpcAddr.nodeAddress().port } } - /** - * Initialize a repo. - * - * @param {import('./types').InitOptions} [initOptions={}] - * @returns {Promise} - */ - async init (initOptions = {}) { + async init (initOptions: InitOptions = {}): Promise { this.initialized = await repoExists(this.path) if (this.initialized) { this.clean = false @@ -142,9 +117,9 @@ class Daemon { initOptions = merge({ emptyRepo: false, - profiles: this.opts.test ? ['test'] : [] + profiles: this.opts.test === true ? ['test'] : [] }, - typeof this.opts.ipfsOptions.init === 'boolean' ? {} : this.opts.ipfsOptions.init, + typeof this.opts.ipfsOptions?.init === 'boolean' ? {} : this.opts.ipfsOptions?.init, typeof initOptions === 'boolean' ? {} : initOptions ) @@ -158,6 +133,10 @@ class Daemon { const args = buildInitArgs(opts) + if (this.exec == null) { + throw new Error('No executable specified') + } + const { stdout, stderr } = await execa(this.exec, args, { env: this.env }) @@ -170,7 +149,7 @@ class Daemon { if (this.opts.type === 'go') { await this._replaceConfig(merge( await this._getConfig(), - this.opts.ipfsOptions.config + this.opts.ipfsOptions?.config )) } @@ -204,10 +183,10 @@ class Daemon { // Check if a daemon is already running const api = checkForRunningApi(this.path) - if (api) { + if (api != null) { this._setApi(api) this._createApi() - } else if (!this.exec) { + } else if (this.exec == null) { throw new Error('No executable specified') } else { const args = buildStartArgs(this.opts) @@ -215,45 +194,46 @@ class Daemon { let output = '' const ready = new Promise((resolve, reject) => { + if (this.exec == null) { + return reject(new Error('No executable specified')) + } + this.subprocess = execa(this.exec, args, { env: this.env }) const { stdout, stderr } = this.subprocess - if (!stderr) { + if (stderr == null) { throw new Error('stderr was not defined on subprocess') } - if (!stdout) { + if (stdout == null) { throw new Error('stderr was not defined on subprocess') } stderr.on('data', data => daemonLog.err(data.toString())) stdout.on('data', data => daemonLog.info(data.toString())) - /** - * @param {Buffer} data - */ - const readyHandler = data => { + const readyHandler = (data: Buffer) => { output += data.toString() const apiMatch = output.trim().match(/API .*listening on:? (.*)/) const gwMatch = output.trim().match(/Gateway .*listening on:? (.*)/) const grpcMatch = output.trim().match(/gRPC .*listening on:? (.*)/) - if (apiMatch && apiMatch.length > 0) { + if ((apiMatch != null) && apiMatch.length > 0) { this._setApi(apiMatch[1]) } - if (gwMatch && gwMatch.length > 0) { + if ((gwMatch != null) && gwMatch.length > 0) { this._setGateway(gwMatch[1]) } - if (grpcMatch && grpcMatch.length > 0) { + if ((grpcMatch != null) && grpcMatch.length > 0) { this._setGrpc(grpcMatch[1]) } - if (output.match(/(?:daemon is running|Daemon is ready)/)) { + if (output.match(/(?:daemon is running|Daemon is ready)/) != null) { // we're good this._createApi() this.started = true @@ -263,7 +243,7 @@ class Daemon { } stdout.on('data', readyHandler) this.subprocess.catch(err => reject(translateError(err))) - this.subprocess.on('exit', () => { + void this.subprocess.on('exit', () => { this.started = false stderr.removeAllListeners() stdout.removeAllListeners() @@ -285,21 +265,14 @@ class Daemon { return this } - /** - * Stop the daemon. - * - * @param {object} [options] - * @param {number} [options.timeout=60000] - How long to wait for the daemon to stop - * @returns {Promise} - */ - async stop (options = {}) { - const timeout = options.timeout || 60000 + async stop (options: { timeout?: number } = {}): Promise { + const timeout = options.timeout ?? 60000 if (!this.started) { return this } - if (this.subprocess) { + if (this.subprocess != null) { /** @type {ReturnType | undefined} */ let killTimeout const subprocess = this.subprocess @@ -312,8 +285,8 @@ class Daemon { if (this.opts.forceKill !== false) { killTimeout = setTimeout(() => { // eslint-disable-next-line no-console - console.error(new Error(`Timeout stopping ${this.opts.type} node after ${this.opts.forceKillTimeout}ms. Process ${subprocess.pid} will be force killed now.`)) - this.subprocess && this.subprocess.kill('SIGKILL') + console.error(new Error(`Timeout stopping ${this.opts.type ?? 'unknown'} node after ${this.opts.forceKillTimeout ?? 'unknown'}ms. Process ${subprocess.pid ?? 'unknown'} will be force killed now.`)) + this.subprocess?.kill('SIGKILL') }, this.opts.forceKillTimeout) } @@ -325,7 +298,7 @@ class Daemon { timeout }) - if (killTimeout) { + if (killTimeout != null) { clearTimeout(killTimeout) } @@ -349,9 +322,9 @@ class Daemon { * * @returns {Promise} */ - pid () { - if (this.subprocess && this.subprocess.pid != null) { - return Promise.resolve(this.subprocess.pid) + async pid () { + if (this.subprocess?.pid != null) { + return await Promise.resolve(this.subprocess?.pid) } throw new Error('Daemon process is not running.') } @@ -366,6 +339,10 @@ class Daemon { * @returns {Promise} */ async _getConfig (key = 'show') { + if (this.exec == null) { + throw new Error('No executable specified') + } + const { stdout } = await execa( @@ -385,12 +362,12 @@ class Daemon { /** * Replace the current config with the provided one - * - * @private - * @param {object} config - * @returns {Promise} */ - async _replaceConfig (config) { + private async _replaceConfig (config: any): Promise { + if (this.exec == null) { + throw new Error('No executable specified') + } + const tmpFile = path.join(os.tmpdir(), nanoid()) await fs.writeFile(tmpFile, JSON.stringify(config)) @@ -405,12 +382,11 @@ class Daemon { return this } - /** - * Get the version of ipfs - * - * @returns {Promise} - */ - async version () { + async version (): Promise { + if (this.exec == null) { + throw new Error('No executable specified') + } + const { stdout } = await execa(this.exec, ['version'], { diff --git a/src/ipfsd-in-proc.ts b/src/ipfsd-in-proc.ts index c2ee70ce..50404f62 100644 --- a/src/ipfsd-in-proc.ts +++ b/src/ipfsd-in-proc.ts @@ -1,7 +1,8 @@ -import { multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import mergeOptions from 'merge-options' import { repoExists, removeRepo, checkForRunningApi, tmpDir, defaultRepo } from './utils.js' import { logger } from '@libp2p/logger' +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) @@ -9,33 +10,35 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } -/** - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').InitOptions} InitOptions - * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr - */ /** * Controller for in process nodes */ -class InProc { - /** - * @param {Required} opts - */ - constructor (opts) { +class InProc implements Controller { + public path: string + // @ts-expect-error set during startup + public api: IPFSAPI + public subprocess: null + public opts: ControllerOptions + public initialized: boolean + public started: boolean + public clean: boolean + // @ts-expect-error set during startup + public apiAddr: Multiaddr + + private initOptions: InitOptions + private readonly disposable: boolean + private _peerId: PeerData | null + + constructor (opts: ControllerOptions) { this.opts = opts - this.path = this.opts.ipfsOptions.repo || (opts.disposable ? tmpDir(opts.type) : defaultRepo(opts.type)) - this.initOptions = toInitOptions(opts.ipfsOptions.init) - this.disposable = opts.disposable + this.path = this.opts.ipfsOptions?.repo ?? (opts.disposable === true ? tmpDir(opts.type) : defaultRepo(opts.type)) + this.initOptions = toInitOptions(opts.ipfsOptions?.init) + this.disposable = Boolean(opts.disposable) this.initialized = false this.started = false this.clean = true - /** @type {Multiaddr} */ - this.apiAddr // eslint-disable-line no-unused-expressions - this.api = null - /** @type {import('./types').Subprocess | null} */ this.subprocess = null - /** @type {import('./types').PeerData | null} */ this._peerId = null } @@ -48,7 +51,7 @@ class InProc { } async setExec () { - if (this.api !== null) { + if (this.api != null) { return } @@ -62,24 +65,14 @@ class InProc { }) } - /** - * @private - * @param {string} addr - */ - _setApi (addr) { + private _setApi (addr: string): void { this.apiAddr = multiaddr(addr) this.api = this.opts.ipfsHttpModule.create(addr) this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } - /** - * Initialize a repo. - * - * @param {import('./types').InitOptions} [initOptions={}] - * @returns {Promise} - */ - async init (initOptions = {}) { + async init (initOptions: InitOptions = {}): Promise { this.initialized = await repoExists(this.path) if (this.initialized) { this.clean = false @@ -90,7 +83,7 @@ class InProc { this.initOptions = merge( { emptyRepo: false, - profiles: this.opts.test ? ['test'] : [] + profiles: this.opts.test === true ? ['test'] : [] }, this.initOptions, toInitOptions(initOptions) @@ -102,14 +95,7 @@ class InProc { return this } - /** - * Delete the repo that was being used. - * If the node was marked as `disposable` this will be called - * automatically when the process is exited. - * - * @returns {Promise} - */ - async cleanup () { + async cleanup (): Promise { if (!this.clean) { await removeRepo(this.path) this.clean = true @@ -117,15 +103,10 @@ class InProc { return this } - /** - * Start the daemon. - * - * @returns {Promise} - */ - async start () { + async start (): Promise { // Check if a daemon is already running const api = checkForRunningApi(this.path) - if (api) { + if (api != null) { this._setApi(api) } else { await this.setExec() @@ -140,12 +121,7 @@ class InProc { return this } - /** - * Stop the daemon. - * - * @returns {Promise} - */ - async stop () { + async stop (): Promise { if (!this.started) { return this } @@ -161,19 +137,12 @@ class InProc { /** * Get the pid of the `ipfs daemon` process - * - * @returns {Promise} */ - pid () { - return Promise.reject(new Error('not implemented')) + async pid (): Promise { + return await Promise.reject(new Error('not implemented')) } - /** - * Get the version of ipfs - * - * @returns {Promise} - */ - async version () { + async version (): Promise { await this.setExec() const { version } = await this.api.version() @@ -182,10 +151,7 @@ class InProc { } } -/** - * @param {boolean | InitOptions} [init] - */ -const toInitOptions = (init = {}) => +const toInitOptions = (init: boolean | InitOptions = {}): InitOptions => typeof init === 'boolean' ? {} : init export default InProc diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index fcc1d4af..00000000 --- a/src/types.ts +++ /dev/null @@ -1,209 +0,0 @@ - -import type { EventEmitter } from 'events' -import type { IPFS } from 'ipfs-core-types' -import type { Multiaddr } from '@multiformats/multiaddr' -import type { PeerId } from '@libp2p/interface-peer-id' - -export interface Subprocess { - stderr: EventEmitter | null - stdout: EventEmitter | null -} - -export interface PeerData { - id: PeerId - addresses: Multiaddr[] -} - -export interface Controller { - init: (options?: InitOptions) => Promise - start: () => Promise - stop: () => Promise - cleanup: () => Promise - pid: () => Promise - version: () => Promise - path: string - started: boolean - initialized: boolean - clean: boolean - api: IPFS - subprocess?: Subprocess | null - opts: ControllerOptions - apiAddr: Multiaddr - peer: PeerData -} - -export interface RemoteState { - id: string - path: string - initialized: boolean - started: boolean - disposable: boolean - clean: boolean - apiAddr: string - gatewayAddr: string - grpcAddr: string -} - -export type NodeType = 'js' | 'go' | 'proc' - -export interface InitOptions { - pass?: string - bits?: number - algorithm?: string - emptyRepo?: boolean - profiles?: string[] - allowNew?: boolean - privateKey?: string -} - -export interface PreloadOptions { - enabled?: boolean - addresses?: string[] -} - -export interface ExperimentalOptions { - sharding?: boolean - ipnsPubsub?: boolean -} - -export interface CircuitRelayHopOptions { - enabled: boolean - active: boolean -} - -export interface CircuitRelayOptions { - enabled: boolean - hop: CircuitRelayHopOptions -} - -export interface IPFSOptions { - /** - * 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. - */ - repo?: string | any - /** - * 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 - */ - init?: boolean | InitOptions - /** - * If false, do not automatically start the IPFS node. Instead, you’ll need to manually call node.start() yourself. - */ - start?: boolean - /** - * A passphrase to encrypt/decrypt your keys. - */ - pass?: string - /** - * Prevents all logging output from the IPFS node. - */ - silent?: boolean - /** - * Configure circuit relay. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsrelay - */ - relay?: any - /** - * 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 - */ - preload?: boolean | PreloadOptions - /** - * Enable and configure experimental features. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsexperimental - */ - EXPERIMENTAL?: ExperimentalOptions - /** - * 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 - */ - config?: any - /** - * 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 - */ - ipld?: any - /** - * 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 - */ - libp2p?: any - /** - * Configure the libp2p connection manager. https://github.com/ipfs/js-ipfs/blob/master/README.md#optionsconnectionmanager - */ - connectionManager?: any - /** - * Run the node offline - */ - offline?: boolean - /** - * Perform any required repo migrations - */ - repoAutoMigrate?: boolean -} - -export interface ControllerOptions { - /** - * Flag to activate custom config for tests - */ - test?: boolean - /** - * Use remote endpoint to spawn the controllers. Defaults to `true` when not in node - */ - remote?: boolean - /** - * Endpoint URL to manage remote Controllers. (Defaults: 'http://localhost:43134') - */ - endpoint?: string - /** - * A new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits - */ - disposable?: boolean - /** - * The daemon type - */ - type?: NodeType - /** - * Additional environment variables, passed to executing shell. Only applies for Daemon controllers - */ - env?: Record - /** - * Custom cli args - */ - args?: string[] - /** - * Reference to an ipfs-http-client module - */ - ipfsHttpModule?: any - /** - * Reference to an ipfs or ipfs-core module - */ - ipfsModule?: any - /** - * Reference to an ipfs-core module - */ - ipfsClientModule?: any - /** - * Path to a IPFS executable - */ - ipfsBin?: string - /** - * Options for the IPFS node - */ - ipfsOptions?: IPFSOptions - /** - * Whether to use SIGKILL to quit a daemon that does not stop after `.stop()` is called. (default true) - */ - forceKill?: boolean - /** - * How long to wait before force killing a daemon in ms. (default 5000) - */ - forceKillTimeout?: number -} - -export interface ControllerOptionsOverrides { - js?: ControllerOptions - go?: ControllerOptions - proc?: ControllerOptions -} - -export interface Factory { - tmpDir: (options?: ControllerOptions) => Promise - spawn: (options?: ControllerOptions) => Promise - clean: () => Promise - controllers: Controller[] - opts: ControllerOptions -} diff --git a/src/utils.browser.ts b/src/utils.browser.ts index caf17ca3..ab4086e2 100644 --- a/src/utils.browser.ts +++ b/src/utils.browser.ts @@ -1,11 +1,7 @@ import { nanoid } from 'nanoid' -/** - * @param {string} path - * @returns {Promise} - */ -const deleteDb = (path) => { - return new Promise((resolve, reject) => { +const deleteDb = async (path: string): Promise => { + return await new Promise((resolve, reject) => { const keys = self.indexedDB.deleteDatabase(path) keys.onerror = (err) => reject(err) keys.onsuccess = () => resolve() @@ -14,10 +10,8 @@ const deleteDb = (path) => { /** * close repoPath , repoPath/keys, repoPath/blocks and repoPath/datastore - * - * @param {string} repoPath */ -export const removeRepo = async (repoPath) => { +export const removeRepo = async (repoPath: string): Promise => { await deleteDb(repoPath) await deleteDb(repoPath + '/keys') await deleteDb(repoPath + '/blocks') @@ -27,8 +21,8 @@ export const removeRepo = async (repoPath) => { /** * @param {string} repoPath */ -export const repoExists = (repoPath) => { - return new Promise((resolve, reject) => { +export const repoExists = async (repoPath: string): Promise => { + return await new Promise((resolve, reject) => { const req = self.indexedDB.open(repoPath) let existed = true req.onerror = () => reject(req.error) @@ -43,14 +37,14 @@ export const repoExists = (repoPath) => { }) } -export const defaultRepo = () => { +export const defaultRepo = (): string => { return 'ipfs' } -export const checkForRunningApi = () => { +export const checkForRunningApi = (): string | null => { return null } -export const tmpDir = (type = '') => { +export const tmpDir = (type = ''): string => { return `${type}_ipfs_${nanoid()}` } diff --git a/src/utils.ts b/src/utils.ts index f088da00..947cccbb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,13 +4,11 @@ import fs from 'fs' import { logger } from '@libp2p/logger' import { nanoid } from 'nanoid' import tempWrite from 'temp-write' +import type { ControllerOptions, IPFSOptions, NodeType } from '.' const log = logger('ipfsd-ctl:utils') -/** - * @param {string} repoPath - */ -export const removeRepo = async (repoPath) => { +export const removeRepo = async (repoPath: string): Promise => { try { await fs.promises.rm(repoPath, { recursive: true @@ -20,17 +18,11 @@ export const removeRepo = async (repoPath) => { } } -/** - * @param {string} repoPath - */ -export const repoExists = (repoPath) => { - return Promise.resolve(fs.existsSync(path.join(repoPath, 'config'))) +export const repoExists = async (repoPath: string): Promise => { + return await Promise.resolve(fs.existsSync(path.join(repoPath, 'config'))) } -/** - * @param {import('./types').NodeType} [type] - */ -export const defaultRepo = (type) => { +export const defaultRepo = (type?: NodeType): string => { if (process.env.IPFS_PATH !== undefined) { return process.env.IPFS_PATH } @@ -40,10 +32,7 @@ export const defaultRepo = (type) => { ) } -/** - * @param {string} [repoPath] - */ -export const checkForRunningApi = (repoPath = '') => { +export const checkForRunningApi = (repoPath = ''): string | null => { let api try { api = fs.readFileSync(path.join(repoPath, 'api')) @@ -51,63 +40,57 @@ export const checkForRunningApi = (repoPath = '') => { log('Unable to open api file') } - return api ? api.toString() : null + return (api != null) ? api.toString() : null } -export const tmpDir = (type = '') => { +export const tmpDir = (type = ''): string => { return path.join(os.tmpdir(), `${type}_ipfs_${nanoid()}`) } -/** - * @param {import('./types').ControllerOptions} opts - */ -export function buildInitArgs (opts = {}) { +export function buildInitArgs (opts: ControllerOptions = {}): string[] { const args = ['init'] - const ipfsOptions = opts.ipfsOptions || {} - const initOptions = ipfsOptions.init && typeof ipfsOptions.init !== 'boolean' ? ipfsOptions.init : {} + const ipfsOptions: IPFSOptions = opts.ipfsOptions ?? {} + const initOptions = ipfsOptions.init != null && typeof ipfsOptions.init !== 'boolean' ? ipfsOptions.init : {} // default-config only for JS if (opts.type === 'js') { - if (ipfsOptions.config) { + if (ipfsOptions.config != null) { args.push(tempWrite.sync(JSON.stringify(ipfsOptions.config))) } - if (initOptions.pass) { - args.push('--pass', '"' + initOptions.pass + '"') + if (initOptions.pass != null) { + args.push('--pass', `"${initOptions.pass}"`) } } // Translate IPFS options to cli args - if (initOptions.bits) { + if (initOptions.bits != null) { args.push('--bits', `${initOptions.bits}`) } - if (initOptions.algorithm) { + if (initOptions.algorithm != null) { args.push('--algorithm', initOptions.algorithm) } - if (initOptions.emptyRepo) { + if (initOptions.emptyRepo === true) { args.push('--empty-repo') } - if (Array.isArray(initOptions.profiles) && initOptions.profiles.length) { + if (Array.isArray(initOptions.profiles) && initOptions.profiles.length > 0) { args.push('--profile', initOptions.profiles.join(',')) } return args } -/** - * @param {import('./types').ControllerOptions} opts - */ -export function buildStartArgs (opts = {}) { - const ipfsOptions = opts.ipfsOptions || {} - const customArgs = opts.args || [] +export function buildStartArgs (opts: ControllerOptions = {}): string[] { + const ipfsOptions: IPFSOptions = opts.ipfsOptions ?? {} + const customArgs: string[] = opts.args ?? [] const args = ['daemon'].concat(customArgs) if (opts.type === 'js') { - if (ipfsOptions.pass) { + if (ipfsOptions.pass != null) { args.push('--pass', '"' + ipfsOptions.pass + '"') } @@ -115,20 +98,20 @@ export function buildStartArgs (opts = {}) { args.push('--enable-preload', Boolean(typeof ipfsOptions.preload === 'boolean' ? ipfsOptions.preload : ipfsOptions.preload.enabled).toString()) } - if (ipfsOptions.EXPERIMENTAL && ipfsOptions.EXPERIMENTAL.sharding) { + if ((ipfsOptions.EXPERIMENTAL != null) && ipfsOptions.EXPERIMENTAL.sharding === true) { args.push('--enable-sharding-experiment') } } - if (ipfsOptions.offline) { + if (ipfsOptions.offline === true) { args.push('--offline') } - if (ipfsOptions.EXPERIMENTAL && ipfsOptions.EXPERIMENTAL.ipnsPubsub) { + if ((ipfsOptions.EXPERIMENTAL != null) && ipfsOptions.EXPERIMENTAL.ipnsPubsub === true) { args.push('--enable-namesys-pubsub') } - if (ipfsOptions.repoAutoMigrate) { + if (ipfsOptions.repoAutoMigrate === true) { args.push('--migrate') } diff --git a/test/controller.spec.ts b/test/controller.spec.ts index 80b539b2..c54d497e 100644 --- a/test/controller.spec.ts +++ b/test/controller.spec.ts @@ -1,24 +1,19 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable no-loop-func */ import { expect } from 'aegir/chai' import merge from 'merge-options' -import { createFactory, createController } from '../src/index.js' +import { createFactory, createController, ControllerOptions, Factory } from '../src/index.js' import { repoExists } from '../src/utils.js' import { isBrowser, isWebWorker, isNode } from 'wherearewe' import waitFor from 'p-wait-for' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' -/** - * @typedef {import('../src/types').ControllerOptions} ControllerOptions - */ - -/** - * @type {ControllerOptions[]} - */ -const types = [{ +const types: ControllerOptions[] = [{ type: 'js', ipfsOptions: { init: false, @@ -52,25 +47,29 @@ const types = [{ } }] -describe('Controller API', async function () { +describe('Controller API', function () { this.timeout(60000) - const factory = createFactory({ - test: true, - ipfsHttpModule, - ipfsModule: (await import('ipfs')) - }, { - js: { - ipfsBin: isNode ? ipfsModule.path() : undefined - }, - go: { - ipfsBin: isNode ? goIpfsModule.path() : undefined - } - }) + let factory: Factory + + before(async () => { + factory = createFactory({ + test: true, + ipfsHttpModule, + ipfsModule: (await import('ipfs')) + }, { + js: { + ipfsBin: isNode ? ipfsModule.path() : undefined + }, + go: { + ipfsBin: isNode ? goIpfsModule.path() : undefined + } + }) - before(() => factory.spawn({ type: 'js' })) + await factory.spawn({ type: 'js' }) + }) - after(() => factory.clean()) + after(async () => await factory.clean()) describe('init', () => { describe('should work with defaults', () => { @@ -335,10 +334,10 @@ describe('Controller API', async function () { await ctl.init() await ctl.start() await ctl.stop() - if (ctl.subprocess && ctl.subprocess.stderr) { + if (ctl.subprocess?.stderr != null) { expect(ctl.subprocess.stderr.listeners('data')).to.be.empty() } - if (ctl.subprocess && ctl.subprocess.stdout) { + if (ctl.subprocess?.stdout != null) { expect(ctl.subprocess.stdout.listeners('data')).to.be.empty() } }) diff --git a/test/create.spec.ts b/test/create.spec.ts index e955b186..6fcc4484 100644 --- a/test/create.spec.ts +++ b/test/create.spec.ts @@ -1,22 +1,19 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { expect } from 'aegir/chai' import { isNode, isBrowser, isWebWorker } from 'wherearewe' -import { createFactory, createController, createServer } from '../src/index.js' +import { createFactory, createController, createServer, ControllerOptions } from '../src/index.js' import Client from '../src/ipfsd-client.js' import Daemon from '../src/ipfsd-daemon.js' import Proc from '../src/ipfsd-in-proc.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsClientModule from 'ipfs-client' -/** - * @typedef {import('../src/types').ControllerOptions} ControllerOptions - */ - -describe('`createController` should return the correct class', async () => { +describe('`createController` should return the correct class', () => { it('for type `js` ', async () => { const f = await createController({ type: 'js', @@ -73,20 +70,14 @@ describe('`createController` should return the correct class', async () => { disposable: false, ipfsModule, ipfsClientModule: { - /** - * @param {any} opts - */ - create: (opts) => { + create: (opts: any) => { clientCreated = true return ipfsClientModule.create(opts) } }, ipfsHttpModule: { - /** - * @param {any} opts - */ - create: async (opts) => { + create: async (opts: any) => { httpCreated = true return ipfsHttpModule.create(opts) @@ -108,20 +99,14 @@ describe('`createController` should return the correct class', async () => { disposable: false, ipfsModule, ipfsClientModule: { - /** - * @param {any} opts - */ - create: (opts) => { + create: (opts: any) => { clientCreated = true return ipfsClientModule.create(opts) } }, ipfsHttpModule: { - /** - * @param {any} opts - */ - create: async (opts) => { + create: async (opts: any) => { httpCreated = true return ipfsHttpModule.create(opts) @@ -140,8 +125,7 @@ const defaultOps = { ipfsHttpModule } -/** @type {ControllerOptions[]} */ -const types = [{ +const types: ControllerOptions[] = [{ ...defaultOps, type: 'js', test: true, diff --git a/test/factory.spec.ts b/test/factory.spec.ts index cc6f38ea..7a1f5f18 100644 --- a/test/factory.spec.ts +++ b/test/factory.spec.ts @@ -1,10 +1,11 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { expect } from 'aegir/chai' import { isNode } from 'wherearewe' -import { createFactory } from '../src/index.js' +import { ControllerOptions, createFactory } from '../src/index.js' import * as ipfsModule from 'ipfs' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsHttpModule from 'ipfs-http-client' @@ -12,14 +13,7 @@ const defaultOps = { ipfsHttpModule } -/** - * @typedef {import('../src/types').ControllerOptions} ControllerOptions - */ - -/** - * @type {ControllerOptions[]} - */ -const types = [{ +const types: ControllerOptions[] = [{ ...defaultOps, type: 'js', test: true, diff --git a/test/node.routes.ts b/test/node.routes.ts index 8b475b06..fef9ef3d 100644 --- a/test/node.routes.ts +++ b/test/node.routes.ts @@ -10,14 +10,8 @@ import * as ipfsHttpModule from 'ipfs-http-client' describe('routes', function () { this.timeout(60000) - /** - * @type {string} - */ - let id - /** - * @type {Hapi.Server} - */ - let server + let id: string + let server: Hapi.Server before(async () => { server = new Hapi.Server({ port: 43134 }) @@ -52,7 +46,7 @@ describe('routes', function () { expect(res).to.have.nested.property('result.apiAddr') expect(res).to.have.nested.property('result.gatewayAddr') - // @ts-ignore res.result is an object + // @ts-expect-error res.result is an object id = res.result.id }) }) diff --git a/test/node.ts b/test/node.ts index 012c72e2..618ea9c5 100644 --- a/test/node.ts +++ b/test/node.ts @@ -1,16 +1,17 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { expect } from 'aegir/chai' import { createFactory } from '../src/index.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import './node.routes.js' import './node.utils.js' -describe('Node specific tests', async function () { +describe('Node specific tests', function () { this.timeout(60000) const factory = createFactory({ diff --git a/tsconfig.json b/tsconfig.json index 8708ca6c..13a35996 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist", - "emitDeclarationOnly": true + "outDir": "dist" }, "include": [ "src", From 3a78c13c0f242b3864f1436435745da6e4a1b407 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 30 Sep 2022 08:42:02 +0100 Subject: [PATCH 18/23] chore: fix tests --- .gitignore | 1 + src/endpoint/server.browser.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ae2e4e24..f09df423 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ logs *.log coverage +.coverage # Runtime data pids diff --git a/src/endpoint/server.browser.ts b/src/endpoint/server.browser.ts index 7f318495..3592fe8e 100644 --- a/src/endpoint/server.browser.ts +++ b/src/endpoint/server.browser.ts @@ -1,10 +1,20 @@ /* eslint-disable no-console */ +import type { ServerInit } from './server.js' + /** * Creates an instance of Server */ class Server { - constructor () { + private readonly options: ServerInit + public port: number + public host: string + + constructor (options: ServerInit = { port: 43134, host: 'localhost' }) { + this.options = options + this.port = this.options.port ?? 43134 + this.host = this.options.host ?? 'localhost' + console.warn('Server not implemented in the browser') } From 86aec670980ed8a8a9d5882916e8132b33bff7b7 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Sat, 1 Oct 2022 08:36:32 +0100 Subject: [PATCH 19/23] chore: apply suggestions from code review --- src/ipfsd-client.ts | 2 +- src/utils.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ipfsd-client.ts b/src/ipfsd-client.ts index 7cc878c2..9d7c6151 100644 --- a/src/ipfsd-client.ts +++ b/src/ipfsd-client.ts @@ -2,7 +2,7 @@ import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import http from 'ipfs-utils/src/http.js' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' -import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData, RemoteState } from '.' +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData, RemoteState } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) diff --git a/src/utils.ts b/src/utils.ts index 947cccbb..12f8ae76 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,7 @@ import fs from 'fs' import { logger } from '@libp2p/logger' import { nanoid } from 'nanoid' import tempWrite from 'temp-write' -import type { ControllerOptions, IPFSOptions, NodeType } from '.' +import type { ControllerOptions, IPFSOptions, NodeType } from './index.js' const log = logger('ipfsd-ctl:utils') @@ -98,7 +98,7 @@ export function buildStartArgs (opts: ControllerOptions = {}): string[] { args.push('--enable-preload', Boolean(typeof ipfsOptions.preload === 'boolean' ? ipfsOptions.preload : ipfsOptions.preload.enabled).toString()) } - if ((ipfsOptions.EXPERIMENTAL != null) && ipfsOptions.EXPERIMENTAL.sharding === true) { + if (ipfsOptions.EXPERIMENTAL?.sharding === true) { args.push('--enable-sharding-experiment') } } From 919e03fb20453cdb9ba4c664e70aba111ba14ba3 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Mon, 3 Oct 2022 15:22:33 -0700 Subject: [PATCH 20/23] fix: resolve issues with js-kubo-rpc-client in typescript --- package.json | 1 + src/config.ts | 4 ++-- src/index.ts | 18 +++++++++--------- src/ipfsd-in-proc.ts | 2 +- src/utils.ts | 4 ++-- test/controller.spec.ts | 1 - test/create.spec.ts | 17 +++++++---------- test/factory.spec.ts | 15 ++++++--------- 8 files changed, 28 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 8e563d8f..7ed9a98b 100644 --- a/package.json +++ b/package.json @@ -152,6 +152,7 @@ "wherearewe": "^2.0.1" }, "devDependencies": { + "@libp2p/interfaces": "^3.0.3", "@types/hapi__hapi": "^20.0.9", "aegir": "^37.0.15", "go-ipfs": "^0.15.0", diff --git a/src/config.ts b/src/config.ts index 52bd4104..d0672e73 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,8 @@ import { isBrowser, isWebWorker } from 'wherearewe' -import type { NodeType } from './index.js' +import type { ControllerType } from './index.js' export interface ConfigInit { - type?: NodeType + type?: ControllerType } export default (init: ConfigInit) => { diff --git a/src/index.ts b/src/index.ts index d44b9dea..1864d805 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,28 +10,30 @@ export interface PeerData { addresses: Multiaddr[] } -export interface Controller { +export type ControllerType = 'js' | 'go' | 'proc' + +export interface Controller { /** * Initialize a repo */ - init: (options?: InitOptions) => Promise + init: (options?: InitOptions) => Promise> /** * Start the daemon */ - start: () => Promise + start: () => Promise> /** * Stop the daemon */ - stop: () => Promise + stop: () => Promise> /** * Delete the repo that was being used. * If the node was marked as `disposable` this will be called * automatically when the process is exited. */ - cleanup: () => Promise + cleanup: () => Promise> /** * Get the pid of the `ipfs daemon` process @@ -68,8 +70,6 @@ export interface RemoteState { grpcAddr: string } -export type NodeType = 'js' | 'go' | 'proc' - export interface InitOptions { pass?: string bits?: number @@ -159,7 +159,7 @@ export interface IPFSOptions { repoAutoMigrate?: boolean } -export interface ControllerOptions { +export interface ControllerOptions { /** * Flag to activate custom config for tests */ @@ -228,7 +228,7 @@ export interface ControllerOptionsOverrides { proc?: ControllerOptions<'proc'> } -export interface Factory { +export interface Factory { tmpDir: (options?: ControllerOptions) => Promise spawn: (options?: ControllerOptions) => Promise> clean: () => Promise diff --git a/src/ipfsd-in-proc.ts b/src/ipfsd-in-proc.ts index 311fe49c..f60d93e3 100644 --- a/src/ipfsd-in-proc.ts +++ b/src/ipfsd-in-proc.ts @@ -10,7 +10,7 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } -const rpcModuleLogger = logger('ipfsd-ctl:client') +const rpcModuleLogger = logger('ipfsd-ctl:proc') /** * Controller for in process nodes diff --git a/src/utils.ts b/src/utils.ts index 947cccbb..621f553f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,7 @@ import fs from 'fs' import { logger } from '@libp2p/logger' import { nanoid } from 'nanoid' import tempWrite from 'temp-write' -import type { ControllerOptions, IPFSOptions, NodeType } from '.' +import type { ControllerOptions, IPFSOptions, ControllerType } from '.' const log = logger('ipfsd-ctl:utils') @@ -22,7 +22,7 @@ export const repoExists = async (repoPath: string): Promise => { return await Promise.resolve(fs.existsSync(path.join(repoPath, 'config'))) } -export const defaultRepo = (type?: NodeType): string => { +export const defaultRepo = (type?: ControllerType): string => { if (process.env.IPFS_PATH !== undefined) { return process.env.IPFS_PATH } diff --git a/test/controller.spec.ts b/test/controller.spec.ts index 19b1987f..3de78576 100644 --- a/test/controller.spec.ts +++ b/test/controller.spec.ts @@ -249,7 +249,6 @@ describe('Controller API', function () { const ctl2 = await createController(merge( opts, addCorrectRpcModule(opts, { - ipfsHttpModule, ipfsModule, test: true, disposable: true, diff --git a/test/create.spec.ts b/test/create.spec.ts index 6fcc4484..0716f05a 100644 --- a/test/create.spec.ts +++ b/test/create.spec.ts @@ -12,6 +12,7 @@ import * as ipfsHttpModule from 'ipfs-http-client' // @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsClientModule from 'ipfs-client' +import * as kuboRpcModule from 'kubo-rpc-client' describe('`createController` should return the correct class', () => { it('for type `js` ', async () => { @@ -33,7 +34,7 @@ describe('`createController` should return the correct class', () => { const f = await createController({ type: 'go', disposable: false, - ipfsHttpModule, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined }) @@ -121,37 +122,33 @@ describe('`createController` should return the correct class', () => { }) }) -const defaultOps = { - ipfsHttpModule -} - const types: ControllerOptions[] = [{ - ...defaultOps, type: 'js', + ipfsHttpModule, test: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', + kuboRpcModule, test: true }, { - ...defaultOps, type: 'proc', + ipfsHttpModule, test: true, ipfsModule }, { - ...defaultOps, type: 'js', + ipfsHttpModule, test: true, remote: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', + kuboRpcModule, test: true, remote: true }] diff --git a/test/factory.spec.ts b/test/factory.spec.ts index 7a1f5f18..10bf2c0c 100644 --- a/test/factory.spec.ts +++ b/test/factory.spec.ts @@ -8,36 +8,33 @@ import * as ipfsModule from 'ipfs' // @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsHttpModule from 'ipfs-http-client' - -const defaultOps = { - ipfsHttpModule -} +import * as kuboRpcModule from 'kubo-rpc-client' const types: ControllerOptions[] = [{ - ...defaultOps, + ipfsHttpModule, type: 'js', test: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, + ipfsHttpModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', test: true }, { - ...defaultOps, + ipfsHttpModule, type: 'proc', test: true, ipfsModule }, { - ...defaultOps, + ipfsHttpModule, type: 'js', remote: true, test: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, + ipfsHttpModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', remote: true, From 06351981ac81314e409a3ed98c85d92b5cdd9dcb Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Mon, 3 Oct 2022 15:29:37 -0700 Subject: [PATCH 21/23] fix: use kuboRpcModule for go in factory.spec.ts --- test/factory.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/factory.spec.ts b/test/factory.spec.ts index 10bf2c0c..b96ff9c0 100644 --- a/test/factory.spec.ts +++ b/test/factory.spec.ts @@ -17,7 +17,7 @@ const types: ControllerOptions[] = [{ ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ipfsHttpModule, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', test: true @@ -34,7 +34,7 @@ const types: ControllerOptions[] = [{ ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ipfsHttpModule, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', remote: true, From d3afe62068a59d6a3053921295c21b612b9737cb Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:36:16 -0700 Subject: [PATCH 22/23] fix: broken build from bad merge --- src/index.ts | 54 ---------------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/src/index.ts b/src/index.ts index c2ff34fc..1864d805 100644 --- a/src/index.ts +++ b/src/index.ts @@ -289,57 +289,3 @@ export const createServer = (options?: number | { port: number }, factoryOptions return createFactory(factoryOptions, factoryOverrides) }) } - -export interface CreateFactory { (): Factory | Promise } - -/** - * Creates a factory - * - * @param {ControllerOptions} [options] - * @param {ControllerOptionsOverrides} [overrides] - * @returns {Factory} - */ -export const createFactory = (options?: ControllerOptions, overrides?: ControllerOptionsOverrides): Factory => { - return new DefaultFactory(options, overrides) -} - -/** - * Creates a node - */ -export const createController = async (options?: ControllerOptions): Promise => { - const f = new DefaultFactory() - return await f.spawn(options) -} - -export interface IPFSAPI extends IPFS { - apiHost?: string - apiPort?: number - gatewayHost?: string - gatewayPort?: number - grpcHost?: string - grpcPort?: number -} - -/** - * Create a Endpoint Server - * - * @param {number | { port: number }} [options] - Configuration options or just the port. - * @param {ControllerOptions} [factoryOptions] - * @param {ControllerOptionsOverrides} [factoryOverrides] - */ -export const createServer = (options?: number | { port: number }, factoryOptions: ControllerOptions = {}, factoryOverrides: ControllerOptionsOverrides = {}) => { - let port: number | undefined - - if (typeof options === 'number') { - port = options - } else if (options != null) { - port = options.port - } - - return new Server({ - port, - host: '127.0.0.1' - }, () => { - return createFactory(factoryOptions, factoryOverrides) - }) -} From 30db4db845738d021418d7246e84aa9e83c24b49 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:01:22 -0700 Subject: [PATCH 23/23] chore: address PR comments --- package.json | 1 - src/index.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/package.json b/package.json index 71004d76..59b54a9c 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,6 @@ "wherearewe": "^2.0.1" }, "devDependencies": { - "@libp2p/interfaces": "^3.0.3", "@types/hapi__hapi": "^20.0.9", "aegir": "^37.0.15", "go-ipfs": "^0.15.0", diff --git a/src/index.ts b/src/index.ts index 1864d805..6cde7e7e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,9 +51,6 @@ export interface Controller { api: IPFSAPI subprocess?: ExecaChildProcess | null opts: ControllerOptions - // api: Type extends 'go' ? import('kubo-rpc-client').IPFSHTTPClient : IPFS - // subprocess?: Subprocess | null - // opts: ControllerOptions apiAddr: Multiaddr peer: PeerData }