UNPKG

libp2p

Version:

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

207 lines • 7.69 kB
import { InvalidParametersError } from '@libp2p/interface'; import { mergeOptions } from '@libp2p/utils/merge-options'; import { trackedMap } from '@libp2p/utils/tracked-map'; import * as errorsJs from './errors.js'; export const DEFAULT_MAX_INBOUND_STREAMS = 32; export const DEFAULT_MAX_OUTBOUND_STREAMS = 64; /** * Responsible for notifying registered protocols of events in the network. */ export class Registrar { log; topologies; handlers; components; constructor(components) { this.components = components; this.log = components.logger.forComponent('libp2p:registrar'); this.topologies = new Map(); components.metrics?.registerMetricGroup('libp2p_registrar_topologies', { calculate: () => { const output = {}; for (const [key, value] of this.topologies) { output[key] = value.size; } return output; } }); this.handlers = trackedMap({ name: 'libp2p_registrar_protocol_handlers', metrics: components.metrics }); this._onDisconnect = this._onDisconnect.bind(this); this._onPeerUpdate = this._onPeerUpdate.bind(this); this._onPeerIdentify = this._onPeerIdentify.bind(this); this.components.events.addEventListener('peer:disconnect', this._onDisconnect); this.components.events.addEventListener('peer:update', this._onPeerUpdate); this.components.events.addEventListener('peer:identify', this._onPeerIdentify); } [Symbol.toStringTag] = '@libp2p/registrar'; getProtocols() { return Array.from(new Set([ ...this.handlers.keys() ])).sort(); } getHandler(protocol) { const handler = this.handlers.get(protocol); if (handler == null) { throw new errorsJs.UnhandledProtocolError(`No handler registered for protocol ${protocol}`); } return handler; } getTopologies(protocol) { const topologies = this.topologies.get(protocol); if (topologies == null) { return []; } return [ ...topologies.values() ]; } /** * Registers the `handler` for each protocol */ async handle(protocol, handler, opts) { if (this.handlers.has(protocol) && opts?.force !== true) { throw new errorsJs.DuplicateProtocolHandlerError(`Handler already registered for protocol ${protocol}`); } const options = mergeOptions.bind({ ignoreUndefined: true })({ maxInboundStreams: DEFAULT_MAX_INBOUND_STREAMS, maxOutboundStreams: DEFAULT_MAX_OUTBOUND_STREAMS }, opts); this.handlers.set(protocol, { handler, options }); // Add new protocol to self protocols in the peer store await this.components.peerStore.merge(this.components.peerId, { protocols: [protocol] }, opts); } /** * Removes the handler for each protocol. The protocol * will no longer be supported on streams. */ async unhandle(protocols, options) { const protocolList = Array.isArray(protocols) ? protocols : [protocols]; protocolList.forEach(protocol => { this.handlers.delete(protocol); }); // Update self protocols in the peer store await this.components.peerStore.patch(this.components.peerId, { protocols: this.getProtocols() }, options); } /** * Register handlers for a set of multicodecs given */ async register(protocol, topology) { if (topology == null) { throw new InvalidParametersError('invalid topology'); } // Create topology const id = `${(Math.random() * 1e9).toString(36)}${Date.now()}`; let topologies = this.topologies.get(protocol); if (topologies == null) { topologies = new Map(); this.topologies.set(protocol, topologies); } topologies.set(id, topology); return id; } /** * Unregister topology */ unregister(id) { for (const [protocol, topologies] of this.topologies.entries()) { if (topologies.has(id)) { topologies.delete(id); if (topologies.size === 0) { this.topologies.delete(protocol); } } } } /** * Remove a disconnected peer from the record */ _onDisconnect(evt) { const remotePeer = evt.detail; const options = { signal: AbortSignal.timeout(5_000) }; void this.components.peerStore.get(remotePeer, options) .then(peer => { for (const protocol of peer.protocols) { const topologies = this.topologies.get(protocol); if (topologies == null) { // no topologies are interested in this protocol continue; } for (const topology of topologies.values()) { if (topology.filter?.has(remotePeer) === false) { continue; } topology.filter?.remove(remotePeer); topology.onDisconnect?.(remotePeer); } } }) .catch(err => { if (err.name === 'NotFoundError') { // peer has not completed identify so they are not in the peer store return; } this.log.error('could not inform topologies of disconnecting peer %p', remotePeer, err); }); } /** * When a peer is updated, if they have removed supported protocols notify any * topologies interested in the removed protocols. */ _onPeerUpdate(evt) { const { peer, previous } = evt.detail; const removed = (previous?.protocols ?? []).filter(protocol => !peer.protocols.includes(protocol)); for (const protocol of removed) { const topologies = this.topologies.get(protocol); if (topologies == null) { // no topologies are interested in this protocol continue; } for (const topology of topologies.values()) { if (topology.filter?.has(peer.id) === false) { continue; } topology.filter?.remove(peer.id); topology.onDisconnect?.(peer.id); } } } /** * After identify has completed and we have received the list of supported * protocols, notify any topologies interested in those protocols. */ _onPeerIdentify(evt) { const protocols = evt.detail.protocols; const connection = evt.detail.connection; const peerId = evt.detail.peerId; for (const protocol of protocols) { const topologies = this.topologies.get(protocol); if (topologies == null) { // no topologies are interested in this protocol continue; } for (const topology of topologies.values()) { if (connection.limits != null && topology.notifyOnLimitedConnection !== true) { continue; } if (topology.filter?.has(peerId) === true) { continue; } topology.filter?.add(peerId); topology.onConnect?.(peerId, connection); } } } } //# sourceMappingURL=registrar.js.map