libp2p
Version:
JavaScript implementation of libp2p, a modular peer to peer network stack
200 lines (159 loc) • 5.3 kB
text/typescript
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)
}