UNPKG

libp2p

Version:

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

200 lines (159 loc) 5.3 kB
import { connectionSymbol, LimitedConnectionError, ConnectionClosedError, ConnectionClosingError } from '@libp2p/interface' import { setMaxListeners } from 'main-event' import type { AbortOptions, Logger, ComponentLogger, Direction, Connection, Stream, ConnectionTimeline, ConnectionStatus, NewStreamOptions, PeerId, ConnectionLimits } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' const CLOSE_TIMEOUT = 500 interface ConnectionInit { remoteAddr: Multiaddr remotePeer: PeerId newStream(protocols: string[], options?: AbortOptions): Promise<Stream> close(options?: AbortOptions): Promise<void> abort(err: Error): void getStreams(): Stream[] status: ConnectionStatus direction: Direction timeline: ConnectionTimeline multiplexer?: string encryption?: string limits?: ConnectionLimits logger: ComponentLogger } /** * An implementation of the js-libp2p connection. * Any libp2p transport should use an upgrader to return this connection. */ export class ConnectionImpl implements Connection { /** * Connection identifier. */ public readonly id: string /** * Observed multiaddr of the remote peer */ public readonly remoteAddr: Multiaddr /** * Remote peer id */ public readonly remotePeer: PeerId public direction: Direction public timeline: ConnectionTimeline public multiplexer?: string public encryption?: string public status: ConnectionStatus public limits?: ConnectionLimits public readonly log: Logger /** * User provided tags * */ public tags: string[] /** * Reference to the new stream function of the multiplexer */ private readonly _newStream: (protocols: string[], options?: NewStreamOptions) => Promise<Stream> /** * Reference to the close function of the raw connection */ private readonly _close: (options?: AbortOptions) => Promise<void> private readonly _abort: (err: Error) => void /** * Reference to the getStreams function of the muxer */ private readonly _getStreams: () => Stream[] /** * An implementation of the js-libp2p connection. * Any libp2p transport should use an upgrader to return this connection. */ constructor (init: ConnectionInit) { const { remoteAddr, remotePeer, newStream, close, abort, getStreams } = init this.id = `${(parseInt(String(Math.random() * 1e9))).toString(36)}${Date.now()}` this.remoteAddr = remoteAddr this.remotePeer = remotePeer this.direction = init.direction this.status = 'open' this.timeline = init.timeline this.multiplexer = init.multiplexer this.encryption = init.encryption this.limits = init.limits this.log = init.logger.forComponent(`libp2p:connection:${this.direction}:${this.id}`) if (this.remoteAddr.getPeerId() == null) { this.remoteAddr = this.remoteAddr.encapsulate(`/p2p/${this.remotePeer}`) } this._newStream = newStream this._close = close this._abort = abort this._getStreams = getStreams this.tags = [] } readonly [Symbol.toStringTag] = 'Connection' readonly [connectionSymbol] = true /** * Get all the streams of the muxer */ get streams (): Stream[] { return this._getStreams() } /** * Create a new stream from this connection */ async newStream (protocols: string | string[], options?: NewStreamOptions): Promise<Stream> { if (this.status === 'closing') { throw new ConnectionClosingError('the connection is being closed') } if (this.status === 'closed') { throw new ConnectionClosedError('the connection is closed') } if (!Array.isArray(protocols)) { protocols = [protocols] } if (this.limits != null && options?.runOnLimitedConnection !== true) { throw new LimitedConnectionError('Cannot open protocol stream on limited connection') } const stream = await this._newStream(protocols, options) stream.direction = 'outbound' return stream } /** * Close the connection */ async close (options: AbortOptions = {}): Promise<void> { if (this.status === 'closed' || this.status === 'closing') { return } this.log('closing connection to %a', this.remoteAddr) this.status = 'closing' if (options.signal == null) { const signal = AbortSignal.timeout(CLOSE_TIMEOUT) setMaxListeners(Infinity, signal) options = { ...options, signal } } try { this.log.trace('closing underlying transport') // close raw connection await this._close(options) this.log.trace('updating timeline with close time') this.status = 'closed' this.timeline.close = Date.now() } catch (err: any) { this.log.error('error encountered during graceful close of connection to %a', this.remoteAddr, err) this.abort(err) } } abort (err: Error): void { if (this.status === 'closed') { return } this.log.error('aborting connection to %a due to error', this.remoteAddr, err) this.status = 'closing' // Abort raw connection this._abort(err) this.status = 'closed' this.timeline.close = Date.now() } } export function createConnection (init: ConnectionInit): Connection { return new ConnectionImpl(init) }