Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: migrate to TS #776

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions .aegir.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createServer } from './src/index.js'
import { createServer } from './dist/src/index.js'
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"]} */
Expand All @@ -20,15 +21,21 @@ export default {
}
},
before: async () => {
const server = createServer(undefined, {
ipfsModule,
ipfsHttpModule
}, {
/**
* @type {import('./src/types.js').ControllerOptions}
*/
let controllerOptions = {
ipfsModule,
}

const server = createServer(undefined, controllerOptions, {
go: {
ipfsBin: goIpfsModule.path()
ipfsBin: goIpfsModule.path(),
kuboRpcModule
},
js: {
ipfsBin: ipfsModule.path()
ipfsBin: ipfsModule.path(),
ipfsHttpModule
}
}
)
Expand Down
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-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.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,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": {
Expand Down
93 changes: 93 additions & 0 deletions src/controller-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Multiaddr, multiaddr } from '@multiformats/multiaddr'
import type { ExecaChildProcess } from 'execa'
// import fs from 'fs/promises'
// import mergeOptions from 'merge-options'
// import { logger } from '@libp2p/logger'
// import { execa } from 'execa'
// import { nanoid } from 'nanoid'
// import path from 'path'
// import os from 'os'
import { tmpDir, defaultRepo } from './utils.js'
// import waitFor from 'p-wait-for'
import type { Controller, ControllerOptions, InitOptions, NodeType, PeerData } from './types'

// const merge = mergeOptions.bind({ ignoreUndefined: true })
abstract class ControllerBase<T extends NodeType = NodeType> implements Controller<T> {
opts: ControllerOptions<T>
path: any
exec: any
env: any
disposable!: boolean
subprocess: null | ExecaChildProcess<string> = null
initialized: boolean = false
started: boolean = false
clean: boolean = true
apiAddr: null | Multiaddr = null
grpcAddr: null | Multiaddr = null
gatewayAddr: null | Multiaddr = null
api!: Controller<T>['api']
_peerId: null | PeerData = null
constructor (opts: ControllerOptions<T>) {
this.opts = opts

this.disposable = this.opts.disposable ?? false
// if (this.opts.ipfsOptions != null) {
this.path = this.opts.ipfsOptions?.repo ?? (this.disposable ? tmpDir(this.opts.type) : defaultRepo(this.opts.type))
// }
// this.path = (Boolean(this.opts.ipfsOptions.repo)) || (opts.disposable ? 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.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
}

abstract init (options?: InitOptions | undefined): Promise<Controller<T>>
abstract start (): Promise<Controller<T>>
abstract stop (): Promise<Controller<T>>
abstract cleanup (): Promise<Controller<T>>
abstract pid (): Promise<number>
abstract version (): Promise<string>

get peer () {
if (this._peerId == null) {
throw new Error('Not started')
}

return this._peerId
}

_setApi (addr: string) {
this.apiAddr = multiaddr(addr)
}

_setGrpc (addr: string) {
this.grpcAddr = multiaddr(addr)
}

_setGateway (addr: string) {
this.gatewayAddr = multiaddr(addr)
}

_createApi (): void {
throw new Error('Not implemented')
}

async _postStart (): Promise<void> {
if (this.api != null) {
this.started = true
// Add `peerId`
this._peerId = await this.api.id()
}
}
}
export default ControllerBase
86 changes: 38 additions & 48 deletions src/factory.js → src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,12 @@ 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, ControllerOptions_RemoteEnabled, IPFSOptions, NodeType } from './types'

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 = {
const defaults: Partial<ControllerOptions<NodeType>> = {
remote: !isNode && !isElectronMain,
endpoint: process.env.IPFSD_CTL_SERVER || 'http://localhost:43134',
disposable: true,
test: false,
type: 'go',
Expand All @@ -29,89 +22,84 @@ const defaults = {
forceKillTimeout: 5000
}

if (defaults.remote === true) {
defaults.endpoint = process.env.IPFSD_CTL_SERVER ?? 'http://localhost:43134'
}

/**
* 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 */
class Factory<T extends NodeType = NodeType> {
opts: ControllerOptions<T>
overrides: ControllerOptionsOverrides
controllers: Array<Controller<NodeType>>

constructor (options: ControllerOptions<T> = {}, overrides: ControllerOptionsOverrides = {}) {
this.opts = merge(defaults, options)

/** @type ControllerOptionsOverrides */
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 = []
}

/**
* Utility method to get a temporary directory
* useful in browsers to be able to generate temp
* repos manually
*
* @param {ControllerOptions} [options]
* @returns {Promise<string>}
*/
async tmpDir (options = {}) {
const opts = merge(this.opts, options)
async tmpDir (options: ControllerOptions<T> = {}): Promise<string> {
const opts: ControllerOptions<T> = 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 as string }) }
)
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 <SpawnRemoteType extends NodeType>(options: IPFSOptions & ControllerOptions<SpawnRemoteType> & ControllerOptions_RemoteEnabled & {type: SpawnRemoteType}): Promise<Controller<SpawnRemoteType>> {
const remoteEndpoint = options.endpoint ?? 'http://localhost:43134'
const opts = {
json: {
...options,
// avoid recursive spawning
remote: false,
ipfsBin: undefined,
ipfsModule: undefined,
ipfsHttpModule: undefined
ipfsHttpModule: undefined,
kuboRpcModule: undefined
}
}

const res = await http.post(
`${options.endpoint}/spawn`,
`${remoteEndpoint}/spawn`,
opts
)
return new ControllerRemote(
options.endpoint,
remoteEndpoint,
await res.json(),
options
)
}

/**
* Spawn an IPFSd Controller
*
* @param {ControllerOptions} options
* @returns {Promise<Controller>}
*/
async spawn (options = { }) {
const type = options.type || this.opts.type || 'go'
const opts = merge(
async spawn <SpawnType extends NodeType | T | 'go' = 'go'>(options: ControllerOptions<SpawnType> = {}): Promise<Controller<SpawnType>> {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing
const type: SpawnType = (options?.type || this.opts.type || 'go') as SpawnType
options = { ...options, type }
const opts: typeof options & {type: SpawnType} = merge(
this.overrides[type],
options
)
Expand All @@ -122,22 +110,22 @@ class Factory {
start: false,
init: false
},
opts.test
opts.test === true
? {
config: testsConfig(opts),
config: testsConfig({ type }),
preload: { enabled: false }
}
: {},
opts.ipfsOptions
)

let ctl
if (opts.type === 'proc') {
let ctl: Controller<SpawnType>
if (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 })
ctl = await this._spawnRemote({ ...opts, ipfsOptions, type })
} else {
// spawn daemon controller
ctl = new ControllerDaemon({ ...opts, ipfsOptions })
Expand All @@ -147,9 +135,11 @@ class Factory {
this.controllers.push(ctl)

// Auto init and start controller
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (opts.disposable && (!options.ipfsOptions || (options.ipfsOptions && options.ipfsOptions.init !== false))) {
await ctl.init(ipfsOptions.init)
}
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (opts.disposable && (!options.ipfsOptions || (options.ipfsOptions && options.ipfsOptions.start !== false))) {
await ctl.start()
}
Expand All @@ -161,7 +151,7 @@ class Factory {
* Stop all controllers
*/
async clean () {
await Promise.all(this.controllers.map(n => n.stop()))
await Promise.all(this.controllers.map(async n => await n.stop()))
this.controllers = []
}
}
Expand Down
48 changes: 0 additions & 48 deletions src/index.js

This file was deleted.

Loading