UNPKG

@waku/core

Version:

TypeScript implementation of the Waku v2 protocol

118 lines 4.46 kB
import { Logger } from "@waku/utils"; import { selectOpenConnection } from "./utils.js"; const STREAM_LOCK_KEY = "consumed"; export class StreamManager { multicodec; libp2p; log; ongoingCreation = new Set(); streamPool = new Map(); constructor(multicodec, libp2p) { this.multicodec = multicodec; this.libp2p = libp2p; this.log = new Logger(`stream-manager:${multicodec}`); this.libp2p.events.addEventListener("peer:update", this.handlePeerUpdateStreamPool); } async getStream(peerId) { const peerIdStr = peerId.toString(); const scheduledStream = this.streamPool.get(peerIdStr); if (scheduledStream) { this.streamPool.delete(peerIdStr); await scheduledStream; } let stream = this.getOpenStreamForCodec(peerId); if (stream) { this.log.info(`Found existing stream peerId=${peerIdStr} multicodec=${this.multicodec}`); this.lockStream(peerIdStr, stream); return stream; } stream = await this.createStream(peerId); this.lockStream(peerIdStr, stream); return stream; } async createStream(peerId, retries = 0) { const connections = this.libp2p.connectionManager.getConnections(peerId); const connection = selectOpenConnection(connections); if (!connection) { throw new Error(`Failed to get a connection to the peer peerId=${peerId.toString()} multicodec=${this.multicodec}`); } let lastError; let stream; for (let i = 0; i < retries + 1; i++) { try { this.log.info(`Attempting to create a stream for peerId=${peerId.toString()} multicodec=${this.multicodec}`); stream = await connection.newStream(this.multicodec); this.log.info(`Created stream for peerId=${peerId.toString()} multicodec=${this.multicodec}`); break; } catch (error) { lastError = error; } } if (!stream) { throw new Error(`Failed to create a new stream for ${peerId.toString()} -- ` + lastError); } return stream; } async createStreamWithLock(peer) { const peerId = peer.id.toString(); if (this.ongoingCreation.has(peerId)) { this.log.info(`Skipping creation of a stream due to lock for peerId=${peerId} multicodec=${this.multicodec}`); return; } try { this.ongoingCreation.add(peerId); await this.createStream(peer.id); } catch (error) { this.log.error(`Failed to createStreamWithLock:`, error); } finally { this.ongoingCreation.delete(peerId); } return; } handlePeerUpdateStreamPool = (evt) => { const { peer } = evt.detail; if (!peer.protocols.includes(this.multicodec)) { return; } const stream = this.getOpenStreamForCodec(peer.id); if (stream) { return; } this.scheduleNewStream(peer); }; scheduleNewStream(peer) { this.log.info(`Scheduling creation of a stream for peerId=${peer.id.toString()} multicodec=${this.multicodec}`); // abandon previous attempt if (this.streamPool.has(peer.id.toString())) { this.streamPool.delete(peer.id.toString()); } this.streamPool.set(peer.id.toString(), this.createStreamWithLock(peer)); } getOpenStreamForCodec(peerId) { const connections = this.libp2p.connectionManager.getConnections(peerId); const connection = selectOpenConnection(connections); if (!connection) { return; } const stream = connection.streams.find((s) => s.protocol === this.multicodec); if (!stream) { return; } const isStreamUnusable = ["done", "closed", "closing"].includes(stream.writeStatus || ""); if (isStreamUnusable || this.isStreamLocked(stream)) { return; } return stream; } lockStream(peerId, stream) { this.log.info(`Locking stream for peerId:${peerId}\tstreamId:${stream.id}`); stream.metadata[STREAM_LOCK_KEY] = true; } isStreamLocked(stream) { return !!stream.metadata[STREAM_LOCK_KEY]; } } //# sourceMappingURL=stream_manager.js.map