UNPKG

@waku/sdk

Version:

A unified SDK for easy creation and management of js-waku nodes.

134 lines (109 loc) 3.8 kB
import type { IdentifyResult, PeerId } from "@libp2p/interface"; import { FilterCodecs, LightPushCodec } from "@waku/core"; import { HealthStatus, IWakuEventEmitter, Libp2p } from "@waku/interfaces"; import { Logger } from "@waku/utils"; type PeerEvent<T> = (_event: CustomEvent<T>) => void; const log = new Logger("health-indicator"); type HealthIndicatorParams = { libp2p: Libp2p; events: IWakuEventEmitter; }; interface IHealthIndicator { start(): void; stop(): void; toValue(): HealthStatus; } export class HealthIndicator implements IHealthIndicator { private readonly libp2p: Libp2p; private readonly events: IWakuEventEmitter; private value: HealthStatus = HealthStatus.Unhealthy; public constructor(params: HealthIndicatorParams) { this.libp2p = params.libp2p; this.events = params.events; this.onPeerIdentify = this.onPeerIdentify.bind(this); this.onPeerDisconnected = this.onPeerDisconnected.bind(this); } public start(): void { log.info("start: adding listeners to libp2p"); this.libp2p.addEventListener( "peer:identify", this.onPeerIdentify as PeerEvent<IdentifyResult> ); this.libp2p.addEventListener( "peer:disconnect", this.onPeerDisconnected as PeerEvent<PeerId> ); } public stop(): void { log.info("stop: removing listeners to libp2p"); this.libp2p.removeEventListener( "peer:identify", this.onPeerIdentify as PeerEvent<IdentifyResult> ); this.libp2p.removeEventListener( "peer:disconnect", this.onPeerDisconnected as PeerEvent<PeerId> ); } public toValue(): HealthStatus { return this.value; } private async onPeerDisconnected(_event: CustomEvent<PeerId>): Promise<void> { log.info(`onPeerDisconnected: received libp2p event`); const connections = this.libp2p.getConnections(); // we handle only Unhealthy here and onPeerIdentify will cover other cases if (connections.length > 0) { log.info("onPeerDisconnected: has connections, ignoring"); } log.info( `onPeerDisconnected: node identified as ${HealthStatus.Unhealthy}` ); this.updateAndDispatchHealthEvent(HealthStatus.Unhealthy); } private async onPeerIdentify( _event: CustomEvent<IdentifyResult> ): Promise<void> { log.info(`onPeerIdentify: received libp2p event`); const connections = this.libp2p.getConnections(); const peers = await Promise.all( connections.map(async (c) => { try { return await this.libp2p.peerStore.get(c.remotePeer); } catch (e) { return null; } }) ); const filterPeers = peers.filter((p) => p?.protocols.includes(FilterCodecs.SUBSCRIBE) ).length; const lightPushPeers = peers.filter((p) => p?.protocols.includes(LightPushCodec) ).length; let newValue; if (filterPeers === 0 || lightPushPeers === 0) { newValue = HealthStatus.Unhealthy; } else if (filterPeers >= 2 && lightPushPeers >= 2) { newValue = HealthStatus.SufficientlyHealthy; } else if (filterPeers === 1 && lightPushPeers === 1) { newValue = HealthStatus.MinimallyHealthy; } else { log.error( `onPeerIdentify: unexpected state, cannot identify health status of the node: Filter:${filterPeers}; LightPush:${lightPushPeers}` ); newValue = this.value; } log.info(`onPeerIdentify: node identified as ${newValue}`); this.updateAndDispatchHealthEvent(newValue); } private updateAndDispatchHealthEvent(newValue: HealthStatus): void { if (this.value !== newValue) { this.value = newValue; this.events.dispatchEvent( new CustomEvent<HealthStatus>("waku:health", { detail: this.value }) ); } } }