ipfs-core
Version:
JavaScript implementation of the IPFS specification
354 lines (301 loc) • 10.1 kB
JavaScript
import mergeOpts from 'merge-options'
import { isTest } from 'ipfs-utils/src/env.js'
import { logger } from '@libp2p/logger'
import errCode from 'err-code'
import { UnixFS } from 'ipfs-unixfs'
import * as dagPB from '@ipld/dag-pb'
import * as dagCBOR from '@ipld/dag-cbor'
import * as dagJSON from '@ipld/dag-json'
import * as dagJOSE from 'dag-jose'
import { identity } from 'multiformats/hashes/identity'
import { bases, hashes, codecs } from 'multiformats/basics'
import { initAssets } from 'ipfs-core-config/init-assets'
import { AlreadyInitializedError } from '../errors.js'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { TimeoutController } from 'timeout-abort-controller'
import { createStart } from './start.js'
import { createStop } from './stop.js'
import { createDns } from './dns.js'
import { createIsOnline } from './is-online.js'
import { createResolve } from './resolve.js'
import { PinAPI } from './pin/index.js'
import { IPNSAPI } from './ipns.js'
import { NameAPI } from './name/index.js'
import { createRefs } from './refs/index.js'
import { createLocal } from './refs/local.js'
import { BitswapAPI } from './bitswap/index.js'
import { BootstrapAPI } from './bootstrap/index.js'
import { BlockAPI } from './block/index.js'
import { RootAPI } from './root.js'
import { createVersion } from './version.js'
import { createId } from './id.js'
import { createConfig } from './config/index.js'
import { DagAPI } from './dag/index.js'
import { createPreloader } from '../preload.js'
import { createMfsPreloader } from '../mfs-preload.js'
import { createFiles } from './files/index.js'
import { KeyAPI } from './key/index.js'
import { ObjectAPI } from './object/index.js'
import { RepoAPI } from './repo/index.js'
import { StatsAPI } from './stats/index.js'
import { Storage } from './storage.js'
import { Network } from './network.js'
import { Service } from '../utils/service.js'
import { SwarmAPI } from './swarm/index.js'
import { createPing } from './ping.js'
import { createDht } from './dht.js'
import { createPubsub } from './pubsub.js'
import { Multicodecs } from 'ipfs-core-utils/multicodecs'
import { Multihashes } from 'ipfs-core-utils/multihashes'
import { Multibases } from 'ipfs-core-utils/multibases'
const mergeOptions = mergeOpts.bind({ ignoreUndefined: true })
const log = logger('ipfs')
const IPNS_INIT_KEYSPACE_TIMEOUT = 30000
/**
* @typedef {import('../types').Options} Options
* @typedef {import('../types').Print} Print
* @typedef {import('./storage')} StorageAPI
* @typedef {import('multiformats/codecs/interface').BlockCodec<any, any>} BlockCodec
* @typedef {import('multiformats/hashes/interface').MultihashHasher} MultihashHasher
* @typedef {import('multiformats/bases/interface').MultibaseCodec<any>} MultibaseCodec
*/
class IPFS {
/**
* @param {object} config
* @param {Print} config.print
* @param {Storage} config.storage
* @param {import('ipfs-core-utils/multicodecs').Multicodecs} config.codecs
* @param {Options} config.options
*/
constructor ({ print, storage, codecs, options }) {
const { peerId, repo, keychain } = storage
const network = Service.create(Network)
const preload = createPreloader(options.preload)
const dns = createDns()
const isOnline = createIsOnline({ network })
// @ts-expect-error This type check fails as options.
// libp2p can be a function, while IPNS router config expects libp2p config
const ipns = new IPNSAPI(options)
/** @type {MultihashHasher[]} */
const multihashHashers = Object.values(hashes);
(options.ipld && options.ipld.hashers ? options.ipld.hashers : []).forEach(hasher => multihashHashers.push(hasher))
this.hashers = new Multihashes({
hashers: multihashHashers,
loadHasher: options.ipld && options.ipld.loadHasher
})
/** @type {MultibaseCodec[]} */
const multibaseCodecs = Object.values(bases);
(options.ipld && options.ipld.bases ? options.ipld.bases : []).forEach(base => multibaseCodecs.push(base))
this.bases = new Multibases({
bases: multibaseCodecs,
loadBase: options.ipld && options.ipld.loadBase
})
const pin = new PinAPI({ repo, codecs })
const block = new BlockAPI({ codecs, hashers: this.hashers, preload, repo })
const name = new NameAPI({
dns,
ipns,
repo,
codecs,
peerId,
isOnline,
keychain,
options
})
const resolve = createResolve({ repo, codecs, bases: this.bases, name })
const dag = new DagAPI({ repo, codecs, hashers: this.hashers, preload })
const refs = Object.assign(createRefs({ repo, codecs, resolve, preload }), {
local: createLocal({ repo: storage.repo })
})
const { add, addAll, cat, get, ls } = new RootAPI({
preload,
repo,
options: options.EXPERIMENTAL,
hashers: this.hashers
})
const files = createFiles({
repo,
preload,
hashers: this.hashers,
options
})
const mfsPreload = createMfsPreloader({
files,
preload,
options: options.preload
})
this.preload = preload
this.name = name
this.ipns = ipns
this.pin = pin
this.resolve = resolve
this.block = block
this.refs = refs
this.start = createStart({
network,
peerId,
repo,
preload,
ipns,
mfsPreload,
print,
keychain,
hashers: this.hashers,
options
})
this.stop = createStop({
network,
preload,
mfsPreload,
ipns,
repo
})
this.dht = createDht({ network, repo, peerId })
this.pubsub = createPubsub({ network, config: options.config })
this.dns = dns
this.isOnline = isOnline
this.id = createId({ network, peerId })
this.version = createVersion({ repo })
this.bitswap = new BitswapAPI({ network })
this.bootstrap = new BootstrapAPI({ repo })
this.config = createConfig({ repo })
this.ping = createPing({ network })
this.add = add
this.addAll = addAll
this.cat = cat
this.get = get
this.ls = ls
this.dag = dag
this.files = files
this.key = new KeyAPI({ keychain })
this.object = new ObjectAPI({ preload, codecs, repo })
this.repo = new RepoAPI({ repo, hashers: this.hashers })
this.stats = new StatsAPI({ repo, network })
this.swarm = new SwarmAPI({ network })
// For the backwards compatibility
Object.defineProperty(this, 'libp2p', {
get () {
const net = network.try()
return net ? net.libp2p : undefined
}
})
// unimplemented methods
const notImplemented = () => Promise.reject(errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED'))
const notImplementedIter = async function * () { throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED') } // eslint-disable-line require-yield
this.commands = notImplemented
this.diag = {
cmds: notImplemented,
net: notImplemented,
sys: notImplemented
}
this.log = {
level: notImplemented,
ls: notImplemented,
tail: notImplementedIter
}
this.mount = notImplemented
this.codecs = codecs
}
/**
* `IPFS.create` will do the initialization. Keep this around for backwards
* compatibility.
*
* @deprecated
*/
async init () { // eslint-disable-line require-await
throw new AlreadyInitializedError()
}
}
/**
* @param {IPFS} ipfs
*/
const addEmptyDir = async (ipfs) => {
const buf = dagPB.encode({
Data: new UnixFS({ type: 'directory' }).marshal(),
Links: []
})
const cid = await ipfs.block.put(buf, {
mhtype: 'sha2-256',
format: 'dag-pb'
})
await ipfs.pin.add(cid)
return cid
}
/**
* @returns {Options}
*/
const getDefaultOptions = () => ({
start: true,
EXPERIMENTAL: {},
preload: {
enabled: !isTest, // preload by default, unless in test env
addresses: [
'/dns4/node0.preload.ipfs.io/https',
'/dns4/node1.preload.ipfs.io/https',
'/dns4/node2.preload.ipfs.io/https',
'/dns4/node3.preload.ipfs.io/https'
]
}
})
/**
* @param {Options} options
*/
export async function create (options = {}) {
options = mergeOptions(getDefaultOptions(), options)
const initOptions = options.init || {}
/**
* @type {BlockCodec}
*/
const id = {
name: identity.name,
code: identity.code,
encode: (id) => id,
decode: (id) => id
}
/** @type {BlockCodec[]} */
const blockCodecs = Object.values(codecs);
[dagPB, dagCBOR, dagJSON, dagJOSE, id].concat((options.ipld && options.ipld.codecs) || []).forEach(codec => blockCodecs.push(codec))
const multicodecs = new Multicodecs({
codecs: blockCodecs,
loadCodec: options.ipld && options.ipld.loadCodec
})
// eslint-disable-next-line no-console
const print = options.silent ? log : console.log
log('creating repo')
const storage = await Storage.start(print, multicodecs, options)
log('getting repo config')
const config = await storage.repo.config.getAll()
const ipfs = new IPFS({
storage,
print,
codecs: multicodecs,
options: { ...options, config }
})
log('starting preload')
await ipfs.preload.start()
log('starting storage')
ipfs.ipns.startOffline(storage)
if (storage.isNew && !initOptions.emptyRepo) {
// add empty unixfs dir object (go-ipfs assumes this exists)
const cid = await addEmptyDir(ipfs)
log('adding default assets')
await initAssets({ addAll: ipfs.addAll, print })
log('initializing IPNS keyspace')
if (storage.peerId.publicKey == null) {
throw errCode(new Error('Public key missing'), 'ERR_MISSING_PUBLIC_KEY')
}
const timeoutController = new TimeoutController(IPNS_INIT_KEYSPACE_TIMEOUT)
try {
await ipfs.ipns.initializeKeyspace(storage.peerId, uint8ArrayFromString(`/ipfs/${cid}`), {
signal: timeoutController.signal
})
} finally {
timeoutController.clear()
}
}
if (options.start !== false) {
log('starting node')
await ipfs.start()
}
return ipfs
}