UNPKG

libp2p-pubsub

Version:
170 lines (150 loc) 4.31 kB
import debug from 'debug' import { EventEmitter } from 'events' import lp from 'it-length-prefixed' import pushable from 'it-pushable' import { pipe } from 'it-pipe' import { source as abortable } from 'abortable-iterator' import AbortController from 'abort-controller' import type { PeerId } from 'libp2p-interfaces/peer-id' import type { MuxedStream } from 'libp2p-interfaces/stream-muxer' const log = Object.assign(debug('libp2p-pubsub:peer-streams'), { error: debug('libp2p-pubsub:peer-streams:err') }) export interface Options { id: PeerId protocol: string } /** * Thin wrapper around a peer's inbound / outbound pubsub streams */ export class PeerStreams extends EventEmitter { public readonly id: PeerId public readonly protocol: string /** * Write stream - it's preferable to use the write method */ public outboundStream: pushable.Pushable<Uint8Array> | undefined /** * Read stream */ public inboundStream: AsyncIterable<Uint8Array> | undefined /** * The raw outbound stream, as retrieved from conn.newStream */ private _rawOutboundStream: MuxedStream | undefined /** * The raw inbound stream, as retrieved from the callback from libp2p.handle */ private _rawInboundStream: MuxedStream | undefined /** * An AbortController for controlled shutdown of the inbound stream */ private readonly _inboundAbortController: AbortController constructor (opts: Options) { super() this.id = opts.id this.protocol = opts.protocol this._inboundAbortController = new AbortController() } /** * Do we have a connection to read from? * * @type {boolean} */ get isReadable () { return Boolean(this.inboundStream) } /** * Do we have a connection to write on? * * @type {boolean} */ get isWritable () { return Boolean(this.outboundStream) } /** * Send a message to this peer. * Throws if there is no `stream` to write to available. */ write (data: Uint8Array) { if (this.outboundStream == null) { const id = this.id.toString() throw new Error('No writable connection to ' + id) } this.outboundStream.push(data) } /** * Attach a raw inbound stream and setup a read stream */ attachInboundStream (stream: MuxedStream) { // Create and attach a new inbound stream // The inbound stream is: // - abortable, set to only return on abort, rather than throw // - transformed with length-prefix transform this._rawInboundStream = stream this.inboundStream = abortable( pipe( this._rawInboundStream, lp.decode() ), this._inboundAbortController.signal, { returnOnAbort: true } ) this.emit('stream:inbound') return this.inboundStream } /** * Attach a raw outbound stream and setup a write stream */ async attachOutboundStream (stream: MuxedStream) { // If an outbound stream already exists, gently close it const _prevStream = this.outboundStream if (this.outboundStream != null) { // End the stream without emitting a close event await this.outboundStream.end() } this._rawOutboundStream = stream this.outboundStream = pushable({ onEnd: (shouldEmit) => { // close writable side of the stream if ((this._rawOutboundStream?.reset) != null) { this._rawOutboundStream.reset() } this._rawOutboundStream = undefined this.outboundStream = undefined if (shouldEmit != null) { this.emit('close') } } }) pipe( this.outboundStream, lp.encode(), this._rawOutboundStream ).catch((err: Error) => { log.error(err) }) // Only emit if the connection is new if (_prevStream == null) { this.emit('stream:outbound') } } /** * Closes the open connection to peer */ close () { // End the outbound stream if (this.outboundStream != null) { this.outboundStream.end() } // End the inbound stream if (this.inboundStream != null) { this._inboundAbortController.abort() } this._rawOutboundStream = undefined this.outboundStream = undefined this._rawInboundStream = undefined this.inboundStream = undefined this.emit('close') } }