UNPKG

libp2p

Version:

JavaScript implementation of libp2p, a modular peer to peer network stack

200 lines (167 loc) • 5.63 kB
import { serviceCapabilities, serviceDependencies, isStartable } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' import { MissingServiceError, UnmetServiceDependenciesError } from './errors.js' import type { Startable, Libp2pEvents, ComponentLogger, NodeInfo, ConnectionProtector, ConnectionGater, ContentRouting, Metrics, PeerId, PeerRouting, PeerStore, PrivateKey, Upgrader } from '@libp2p/interface' import type { AddressManager, ConnectionManager, RandomWalk, Registrar, TransportManager } from '@libp2p/interface-internal' import type { DNS } from '@multiformats/dns' import type { Datastore } from 'interface-datastore' import type { TypedEventTarget } from 'main-event' export interface Components extends Record<string, any>, Startable { peerId: PeerId privateKey: PrivateKey nodeInfo: NodeInfo logger: ComponentLogger events: TypedEventTarget<Libp2pEvents> addressManager: AddressManager peerStore: PeerStore upgrader: Upgrader randomWalk: RandomWalk registrar: Registrar connectionManager: ConnectionManager transportManager: TransportManager connectionGater: ConnectionGater contentRouting: ContentRouting peerRouting: PeerRouting datastore: Datastore connectionProtector?: ConnectionProtector metrics?: Metrics dns?: DNS } export interface ComponentsInit { peerId?: PeerId privateKey?: PrivateKey nodeInfo?: NodeInfo logger?: ComponentLogger events?: TypedEventTarget<Libp2pEvents> addressManager?: AddressManager peerStore?: PeerStore upgrader?: Upgrader randomWalk?: RandomWalk metrics?: Metrics registrar?: Registrar connectionManager?: ConnectionManager transportManager?: TransportManager connectionGater?: ConnectionGater contentRouting?: ContentRouting peerRouting?: PeerRouting datastore?: Datastore connectionProtector?: ConnectionProtector dns?: DNS } class DefaultComponents implements Startable { public components: Record<string, any> = {} private _started = false constructor (init: ComponentsInit = {}) { this.components = {} for (const [key, value] of Object.entries(init)) { this.components[key] = value } if (this.components.logger == null) { this.components.logger = defaultLogger() } } isStarted (): boolean { return this._started } private async _invokeStartableMethod (methodName: 'beforeStart' | 'start' | 'afterStart' | 'beforeStop' | 'stop' | 'afterStop'): Promise<void> { await Promise.all( Object.values(this.components) .filter(obj => isStartable(obj)) .map(async (startable: Startable) => { await startable[methodName]?.() }) ) } async beforeStart (): Promise<void> { await this._invokeStartableMethod('beforeStart') } async start (): Promise<void> { await this._invokeStartableMethod('start') this._started = true } async afterStart (): Promise<void> { await this._invokeStartableMethod('afterStart') } async beforeStop (): Promise<void> { await this._invokeStartableMethod('beforeStop') } async stop (): Promise<void> { await this._invokeStartableMethod('stop') this._started = false } async afterStop (): Promise<void> { await this._invokeStartableMethod('afterStop') } } const OPTIONAL_SERVICES = [ 'metrics', 'connectionProtector', 'dns' ] const NON_SERVICE_PROPERTIES = [ 'components', 'isStarted', 'beforeStart', 'start', 'afterStart', 'beforeStop', 'stop', 'afterStop', 'then', '_invokeStartableMethod' ] export function defaultComponents (init: ComponentsInit = {}): Components { const components = new DefaultComponents(init) const proxy = new Proxy(components, { get (target, prop, receiver) { if (typeof prop === 'string' && !NON_SERVICE_PROPERTIES.includes(prop)) { const service = components.components[prop] if (service == null && !OPTIONAL_SERVICES.includes(prop)) { throw new MissingServiceError(`${prop} not set`) } return service } return Reflect.get(target, prop, receiver) }, set (target, prop, value) { if (typeof prop === 'string') { components.components[prop] = value } else { Reflect.set(target, prop, value) } return true } }) // @ts-expect-error component keys are proxied return proxy } export function checkServiceDependencies (components: Components): void { const serviceCapabilities: Record<string, ConstrainBoolean> = {} for (const service of Object.values(components.components)) { for (const capability of getServiceCapabilities(service)) { serviceCapabilities[capability] = true } } for (const service of Object.values(components.components)) { for (const capability of getServiceDependencies(service)) { if (serviceCapabilities[capability] !== true) { throw new UnmetServiceDependenciesError(`Service "${getServiceName(service)}" required capability "${capability}" but it was not provided by any component, you may need to add additional configuration when creating your node.`) } } } } function getServiceCapabilities (service: any): string[] { if (Array.isArray(service?.[serviceCapabilities])) { return service[serviceCapabilities] } return [] } function getServiceDependencies (service: any): string[] { if (Array.isArray(service?.[serviceDependencies])) { return service[serviceDependencies] } return [] } function getServiceName (service: any): string { return service?.[Symbol.toStringTag] ?? service?.toString() ?? 'unknown' }