libp2p
Version:
JavaScript implementation of libp2p, a modular peer to peer network stack
207 lines • 7.69 kB
JavaScript
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