diff --git a/.aegir.js b/.aegir.js index f94961d0..9aa1d126 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,5 +1,6 @@ import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' +import * as kuboRpcModule from 'kubo-rpc-client' import * as goIpfsModule from 'go-ipfs' /** @type {import('aegir').Options["build"]["config"]} */ @@ -13,13 +14,14 @@ const config = { const server = createServer(undefined, { ipfsModule, - ipfsHttpModule }, { go: { - ipfsBin: goIpfsModule.path() + ipfsBin: goIpfsModule.path(), + kuboRpcModule }, js: { - ipfsBin: ipfsModule.path() + ipfsBin: ipfsModule.path(), + ipfsHttpModule } } ) diff --git a/README.md b/README.md index ba5bc188..a4feed0f 100644 --- a/README.md +++ b/README.md @@ -61,17 +61,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-client`, 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. diff --git a/package.json b/package.json index cb668316..8289afb7 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "ipfs-client": "^0.9.0", "ipfs-core-types": "^0.12.0", "ipfs-http-client": "^58.0.0", + "kubo-rpc-client": "^1.0.1", "util": "^0.12.4" }, "browser": { 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/factory.ts b/src/factory.ts index 1621efb7..1b98bf13 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -82,7 +82,8 @@ class DefaultFactory implements Factory { remote: false, ipfsBin: undefined, ipfsModule: undefined, - ipfsHttpModule: undefined + ipfsHttpModule: undefined, + kuboRpcModule: undefined } } diff --git a/src/index.ts b/src/index.ts index 9ea81245..6cde7e7e 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 @@ -65,8 +67,6 @@ export interface RemoteState { grpcAddr: string } -export type NodeType = 'js' | 'go' | 'proc' - export interface InitOptions { pass?: string bits?: number @@ -156,7 +156,7 @@ export interface IPFSOptions { repoAutoMigrate?: boolean } -export interface ControllerOptions { +export interface ControllerOptions { /** * Flag to activate custom config for tests */ @@ -176,7 +176,7 @@ export interface ControllerOptions { /** * The daemon type */ - type?: NodeType + type?: Type /** * Additional environment variables, passed to executing shell. Only applies for Daemon controllers */ @@ -189,6 +189,10 @@ export interface ControllerOptions { * Reference to an ipfs-http-client module */ ipfsHttpModule?: any + /** + * Reference to a kubo-rpc-client module + */ + kuboRpcModule?: any /** * Reference to an ipfs or ipfs-core module */ @@ -216,17 +220,17 @@ 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 } export interface CreateFactory { (): Factory | Promise } diff --git a/src/ipfsd-client.ts b/src/ipfsd-client.ts index 9d7c6151..7f75ced3 100644 --- a/src/ipfsd-client.ts +++ b/src/ipfsd-client.ts @@ -10,6 +10,7 @@ const daemonLog = { info: logger('ipfsd-ctl:client:stdout'), err: logger('ipfsd-ctl:client:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:client') /** * Controller for remote nodes @@ -84,7 +85,15 @@ class Client implements Controller { http: this.apiAddr }) } else if (this.apiAddr != null) { - this.api = this.opts.ipfsHttpModule.create(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') + } } if (this.api != null) { diff --git a/src/ipfsd-daemon.ts b/src/ipfsd-daemon.ts index 915003b9..25977127 100644 --- a/src/ipfsd-daemon.ts +++ b/src/ipfsd-daemon.ts @@ -16,6 +16,7 @@ const daemonLog = { info: logger('ipfsd-ctl:daemon:stdout'), err: logger('ipfsd-ctl:daemon:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:daemon') function translateError (err: Error & { stdout: string, stderr: string }) { // get the actual error message to be the err.message @@ -85,7 +86,15 @@ class Daemon implements Controller { http: this.apiAddr }) } else if (this.apiAddr != null) { - this.api = this.opts.ipfsHttpModule.create(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') + } } if (this.api == null) { diff --git a/src/ipfsd-in-proc.ts b/src/ipfsd-in-proc.ts index 50404f62..f60d93e3 100644 --- a/src/ipfsd-in-proc.ts +++ b/src/ipfsd-in-proc.ts @@ -10,6 +10,7 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:proc') /** * Controller for in process nodes @@ -67,7 +68,17 @@ class InProc implements Controller { private _setApi (addr: string): void { this.apiAddr = multiaddr(addr) - this.api = this.opts.ipfsHttpModule.create(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') + } + this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } diff --git a/src/utils.ts b/src/utils.ts index 12f8ae76..0e23ea51 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 './index.js' +import type { ControllerOptions, IPFSOptions, ControllerType } from './index.js' 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 c54d497e..3de78576 100644 --- a/test/controller.spec.ts +++ b/test/controller.spec.ts @@ -12,6 +12,7 @@ import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' // @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' +import * as kuboRpcModule from 'kubo-rpc-client' const types: ControllerOptions[] = [{ type: 'js', @@ -21,6 +22,7 @@ const types: ControllerOptions[] = [{ } }, { type: 'go', + kuboRpcModule, ipfsOptions: { init: false, start: false @@ -40,6 +42,7 @@ const types: ControllerOptions[] = [{ } }, { type: 'go', + kuboRpcModule, remote: true, ipfsOptions: { init: false, @@ -47,6 +50,19 @@ const types: ControllerOptions[] = [{ } }] +/** + * Set the options object with the correct RPC module depending on the type + */ +function addCorrectRpcModule (opts: ControllerOptions, additionalOpts: ControllerOptions) { + if (opts.type === 'go') { + additionalOpts.kuboRpcModule = kuboRpcModule + } else { + additionalOpts.ipfsHttpModule = ipfsHttpModule + } + + return additionalOpts +} + describe('Controller API', function () { this.timeout(60000) @@ -55,14 +71,15 @@ describe('Controller API', function () { before(async () => { factory = createFactory({ test: true, - ipfsHttpModule, ipfsModule: (await import('ipfs')) }, { js: { - ipfsBin: isNode ? ipfsModule.path() : undefined + ipfsBin: isNode ? ipfsModule.path() : undefined, + ipfsHttpModule }, go: { - ipfsBin: isNode ? goIpfsModule.path() : undefined + ipfsBin: isNode ? goIpfsModule.path() : undefined, + kuboRpcModule } }) @@ -191,13 +208,12 @@ describe('Controller API', 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() @@ -219,7 +235,7 @@ describe('Controller API', function () { const ctl1 = await createController(merge( { type: 'go', - ipfsHttpModule, + kuboRpcModule, ipfsBin: goIpfsModule.path(), test: true, disposable: true, @@ -232,8 +248,7 @@ describe('Controller API', function () { expect(ctl1.started).to.be.true() const ctl2 = await createController(merge( - opts, { - ipfsHttpModule, + opts, addCorrectRpcModule(opts, { ipfsModule, test: true, disposable: true, @@ -241,7 +256,7 @@ describe('Controller API', function () { repo: ctl1.path, start: true } - } + }) )) expect(ctl2.started).to.be.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..b96ff9c0 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, + kuboRpcModule, 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, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', remote: true,