From c93b58ed8c8d727c93f4958c182fed8b679ba560 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 19 Oct 2024 17:02:38 +0200 Subject: [PATCH 01/22] up lock --- package-lock.json | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index afea4d840b..b819d96306 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40247,14 +40247,14 @@ }, "packages/core": { "name": "@waku/core", - "version": "0.0.32", + "version": "0.0.33", "license": "MIT OR Apache-2.0", "dependencies": { "@libp2p/ping": "^1.1.2", - "@waku/enr": "^0.0.26", - "@waku/interfaces": "0.0.27", + "@waku/enr": "^0.0.27", + "@waku/interfaces": "0.0.28", "@waku/proto": "0.0.8", - "@waku/utils": "0.0.20", + "@waku/utils": "0.0.21", "debug": "^4.3.4", "it-all": "^3.0.4", "it-length-prefixed": "^9.0.4", @@ -40312,14 +40312,14 @@ }, "packages/discovery": { "name": "@waku/discovery", - "version": "0.0.5", + "version": "0.0.6", "license": "MIT OR Apache-2.0", "dependencies": { - "@waku/core": "0.0.32", - "@waku/enr": "0.0.26", - "@waku/interfaces": "0.0.27", + "@waku/core": "0.0.33", + "@waku/enr": "0.0.27", + "@waku/interfaces": "0.0.28", "@waku/proto": "^0.0.8", - "@waku/utils": "0.0.20", + "@waku/utils": "0.0.21", "debug": "^4.3.4", "dns-query": "^0.11.2", "hi-base32": "^0.5.1", @@ -40358,7 +40358,7 @@ }, "packages/enr": { "name": "@waku/enr", - "version": "0.0.26", + "version": "0.0.27", "license": "MIT OR Apache-2.0", "dependencies": { "@ethersproject/rlp": "^5.7.0", @@ -40366,7 +40366,7 @@ "@libp2p/peer-id": "^4.2.1", "@multiformats/multiaddr": "^12.0.0", "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.20", + "@waku/utils": "0.0.21", "debug": "^4.3.4", "js-sha3": "^0.9.2" }, @@ -40378,7 +40378,7 @@ "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", "@waku/build-utils": "*", - "@waku/interfaces": "0.0.27", + "@waku/interfaces": "0.0.28", "chai": "^4.3.10", "cspell": "^8.6.1", "fast-check": "^3.19.0", @@ -40402,7 +40402,7 @@ }, "packages/interfaces": { "name": "@waku/interfaces", - "version": "0.0.27", + "version": "0.0.28", "license": "MIT OR Apache-2.0", "dependencies": { "@waku/proto": "^0.0.8" @@ -40420,14 +40420,14 @@ }, "packages/message-encryption": { "name": "@waku/message-encryption", - "version": "0.0.30", + "version": "0.0.31", "license": "MIT OR Apache-2.0", "dependencies": { "@noble/secp256k1": "^1.7.1", - "@waku/core": "0.0.32", - "@waku/interfaces": "0.0.27", + "@waku/core": "0.0.33", + "@waku/interfaces": "0.0.28", "@waku/proto": "0.0.8", - "@waku/utils": "0.0.20", + "@waku/utils": "0.0.21", "debug": "^4.3.4", "js-sha3": "^0.9.2", "uint8arrays": "^5.0.1" @@ -40453,11 +40453,11 @@ }, "packages/message-hash": { "name": "@waku/message-hash", - "version": "0.1.16", + "version": "0.1.17", "license": "MIT OR Apache-2.0", "dependencies": { "@noble/hashes": "^1.3.2", - "@waku/utils": "0.0.20" + "@waku/utils": "0.0.21" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", @@ -40467,7 +40467,7 @@ "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", "@waku/build-utils": "*", - "@waku/interfaces": "0.0.27", + "@waku/interfaces": "0.0.28", "chai": "^4.3.10", "cspell": "^8.6.1", "fast-check": "^3.19.0", @@ -40526,16 +40526,16 @@ }, "packages/relay": { "name": "@waku/relay", - "version": "0.0.15", + "version": "0.0.16", "license": "MIT OR Apache-2.0", "dependencies": { "@chainsafe/libp2p-gossipsub": "^13.1.0", "@noble/hashes": "^1.3.2", - "@waku/core": "0.0.32", - "@waku/interfaces": "0.0.27", + "@waku/core": "0.0.33", + "@waku/interfaces": "0.0.28", "@waku/proto": "0.0.8", - "@waku/sdk": "0.0.28", - "@waku/utils": "0.0.20", + "@waku/sdk": "0.0.29", + "@waku/utils": "0.0.21", "chai": "^4.3.10", "debug": "^4.3.4", "fast-check": "^3.19.0", @@ -40562,7 +40562,7 @@ }, "packages/sdk": { "name": "@waku/sdk", - "version": "0.0.28", + "version": "0.0.29", "license": "MIT OR Apache-2.0", "dependencies": { "@chainsafe/libp2p-noise": "^15.1.0", @@ -40572,12 +40572,12 @@ "@libp2p/ping": "^1.1.2", "@libp2p/websockets": "^8.1.4", "@noble/hashes": "^1.3.3", - "@waku/core": "0.0.32", - "@waku/discovery": "0.0.5", - "@waku/interfaces": "0.0.27", - "@waku/message-hash": "0.1.16", + "@waku/core": "0.0.33", + "@waku/discovery": "0.0.6", + "@waku/interfaces": "0.0.28", + "@waku/message-hash": "0.1.17", "@waku/proto": "^0.0.8", - "@waku/utils": "0.0.20", + "@waku/utils": "0.0.21", "async-mutex": "^0.5.0", "libp2p": "^1.8.1" }, @@ -40745,11 +40745,11 @@ }, "packages/utils": { "name": "@waku/utils", - "version": "0.0.20", + "version": "0.0.21", "license": "MIT OR Apache-2.0", "dependencies": { "@noble/hashes": "^1.3.2", - "@waku/interfaces": "0.0.27", + "@waku/interfaces": "0.0.28", "chai": "^4.3.10", "debug": "^4.3.4", "uint8arrays": "^5.0.1" From 7efbd266094bc631fb55ddf38c7fc9e2f416586b Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 19 Oct 2024 17:16:10 +0200 Subject: [PATCH 02/22] make ConnectionManager use ctor --- packages/core/src/lib/connection_manager.ts | 59 +++++++------------ packages/interfaces/src/connection_manager.ts | 2 +- packages/sdk/src/protocols/filter/index.ts | 2 +- .../src/protocols/light_push/light_push.ts | 5 +- packages/sdk/src/protocols/store/index.ts | 2 +- packages/sdk/src/waku/waku.ts | 11 ++-- 6 files changed, 29 insertions(+), 52 deletions(-) diff --git a/packages/core/src/lib/connection_manager.ts b/packages/core/src/lib/connection_manager.ts index 8fc61f4d54..fda6ff06bb 100644 --- a/packages/core/src/lib/connection_manager.ts +++ b/packages/core/src/lib/connection_manager.ts @@ -27,11 +27,21 @@ export const DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED = 1; export const DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER = 3; export const DEFAULT_MAX_PARALLEL_DIALS = 3; +type ConnectionManagerConstructorOptions = { + libp2p: Libp2p; + keepAliveOptions: KeepAliveOptions; + pubsubTopics: PubsubTopic[]; + relay?: IRelay; + config?: Partial; +}; + export class ConnectionManager extends TypedEventEmitter implements IConnectionManager { - private static instances = new Map(); + // TODO(weboko): make it private + public readonly pubsubTopics: PubsubTopic[]; + private keepAliveManager: KeepAliveManager; private options: ConnectionManagerOptions; private libp2p: Libp2p; @@ -51,29 +61,6 @@ export class ConnectionManager return this.isP2PNetworkConnected; } - public static create( - peerId: string, - libp2p: Libp2p, - keepAliveOptions: KeepAliveOptions, - pubsubTopics: PubsubTopic[], - relay?: IRelay, - options?: ConnectionManagerOptions - ): ConnectionManager { - let instance = ConnectionManager.instances.get(peerId); - if (!instance) { - instance = new ConnectionManager( - libp2p, - keepAliveOptions, - pubsubTopics, - relay, - options - ); - ConnectionManager.instances.set(peerId, instance); - } - - return instance; - } - public stop(): void { this.keepAliveManager.stopAll(); this.libp2p.removeEventListener( @@ -156,27 +143,21 @@ export class ConnectionManager }; } - private constructor( - libp2p: Libp2p, - keepAliveOptions: KeepAliveOptions, - public readonly configuredPubsubTopics: PubsubTopic[], - relay?: IRelay, - options?: Partial - ) { + public constructor(options: ConnectionManagerConstructorOptions) { super(); - this.libp2p = libp2p; - this.configuredPubsubTopics = configuredPubsubTopics; + this.libp2p = options.libp2p; + this.pubsubTopics = options.pubsubTopics; this.options = { maxDialAttemptsForPeer: DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER, maxBootstrapPeersAllowed: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED, maxParallelDials: DEFAULT_MAX_PARALLEL_DIALS, - ...options + ...options.config }; this.keepAliveManager = new KeepAliveManager({ - relay, - libp2p, - options: keepAliveOptions + relay: options.relay, + libp2p: options.libp2p, + options: options.keepAliveOptions }); this.startEventListeners() @@ -478,7 +459,7 @@ export class ConnectionManager log.warn( `Discovered peer ${peerId.toString()} with ShardInfo ${shardInfo} is not part of any of the configured pubsub topics (${ - this.configuredPubsubTopics + this.pubsubTopics }). Not dialing.` ); @@ -573,7 +554,7 @@ export class ConnectionManager const pubsubTopics = shardInfoToPubsubTopics(shardInfo); const isTopicConfigured = pubsubTopics.some((topic) => - this.configuredPubsubTopics.includes(topic) + this.pubsubTopics.includes(topic) ); return isTopicConfigured; } diff --git a/packages/interfaces/src/connection_manager.ts b/packages/interfaces/src/connection_manager.ts index a8a6591fdc..1644f75140 100644 --- a/packages/interfaces/src/connection_manager.ts +++ b/packages/interfaces/src/connection_manager.ts @@ -63,7 +63,7 @@ export interface IConnectionStateEvents { export interface IConnectionManager extends TypedEventEmitter { - configuredPubsubTopics: PubsubTopic[]; + pubsubTopics: PubsubTopic[]; dropConnection(peerId: PeerId): Promise; getPeersByDiscovery(): Promise; stop(): void; diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index 2d8f356150..d5509c2337 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -56,7 +56,7 @@ class Filter extends BaseProtocolSDK implements IFilter { await subscription.processIncomingMessage(wakuMessage, peerIdStr); }, - connectionManager.configuredPubsubTopics, + connectionManager.pubsubTopics, libp2p ), connectionManager, diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/protocols/light_push/light_push.ts index 9764111f18..5919822656 100644 --- a/packages/sdk/src/protocols/light_push/light_push.ts +++ b/packages/sdk/src/protocols/light_push/light_push.ts @@ -41,10 +41,7 @@ export class LightPush implements ILightPush { options?: ProtocolCreateOptions ) { this.numPeersToUse = options?.numPeersToUse ?? DEFAULT_NUM_PEERS_TO_USE; - this.protocol = new LightPushCore( - connectionManager.configuredPubsubTopics, - libp2p - ); + this.protocol = new LightPushCore(connectionManager.pubsubTopics, libp2p); } public async send( diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index 01ef45d58c..322e34f6d3 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -25,7 +25,7 @@ export class Store extends BaseProtocolSDK implements IStore { public constructor(connectionManager: ConnectionManager, libp2p: Libp2p) { super( - new StoreCore(connectionManager.configuredPubsubTopics, libp2p), + new StoreCore(connectionManager.pubsubTopics, libp2p), connectionManager, { numPeersToUse: DEFAULT_NUM_PEERS diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index eea596d7a9..b97244950d 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -95,13 +95,12 @@ export class WakuNode implements IWaku { const peerId = this.libp2p.peerId.toString(); - this.connectionManager = ConnectionManager.create( - peerId, + this.connectionManager = new ConnectionManager({ libp2p, - { pingKeepAlive, relayKeepAlive }, - this.pubsubTopics, - this.relay - ); + keepAliveOptions: { pingKeepAlive, relayKeepAlive }, + pubsubTopics: this.pubsubTopics, + relay: this.relay + }); this.health = getHealthManager(); From a6dd49974cc179dbb5f047446039db45f706fd51 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 19 Oct 2024 18:05:24 +0200 Subject: [PATCH 03/22] reform connection manager configurations --- packages/core/src/lib/connection_manager.ts | 18 ++++++---- packages/core/src/lib/keep_alive_manager.ts | 8 +++-- packages/interfaces/src/connection_manager.ts | 36 +++++++++++++++---- packages/interfaces/src/index.ts | 1 - packages/interfaces/src/keep_alive_manager.ts | 4 --- packages/interfaces/src/protocols.ts | 15 +++++++- packages/sdk/src/waku/waku.ts | 26 ++------------ 7 files changed, 63 insertions(+), 45 deletions(-) delete mode 100644 packages/interfaces/src/keep_alive_manager.ts diff --git a/packages/core/src/lib/connection_manager.ts b/packages/core/src/lib/connection_manager.ts index fda6ff06bb..d8697784f9 100644 --- a/packages/core/src/lib/connection_manager.ts +++ b/packages/core/src/lib/connection_manager.ts @@ -10,7 +10,6 @@ import { IConnectionStateEvents, IPeersByDiscoveryEvents, IRelay, - KeepAliveOptions, PeersByDiscoveryResult, PubsubTopic, ShardInfo @@ -23,13 +22,15 @@ import { KeepAliveManager } from "./keep_alive_manager.js"; const log = new Logger("connection-manager"); -export const DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED = 1; -export const DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER = 3; -export const DEFAULT_MAX_PARALLEL_DIALS = 3; +const DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED = 1; +const DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER = 3; +const DEFAULT_MAX_PARALLEL_DIALS = 3; + +const DEFAULT_PING_KEEP_ALIVE_SEC = 5 * 60; +const DEFAULT_RELAY_KEEP_ALIVE_SEC = 5 * 60; type ConnectionManagerConstructorOptions = { libp2p: Libp2p; - keepAliveOptions: KeepAliveOptions; pubsubTopics: PubsubTopic[]; relay?: IRelay; config?: Partial; @@ -151,13 +152,18 @@ export class ConnectionManager maxDialAttemptsForPeer: DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER, maxBootstrapPeersAllowed: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED, maxParallelDials: DEFAULT_MAX_PARALLEL_DIALS, + pingKeepAlive: DEFAULT_PING_KEEP_ALIVE_SEC, + relayKeepAlive: DEFAULT_RELAY_KEEP_ALIVE_SEC, ...options.config }; this.keepAliveManager = new KeepAliveManager({ relay: options.relay, libp2p: options.libp2p, - options: options.keepAliveOptions + options: { + pingKeepAlive: this.options.pingKeepAlive, + relayKeepAlive: this.options.relayKeepAlive + } }); this.startEventListeners() diff --git a/packages/core/src/lib/keep_alive_manager.ts b/packages/core/src/lib/keep_alive_manager.ts index 27754b6b38..3f606acd9b 100644 --- a/packages/core/src/lib/keep_alive_manager.ts +++ b/packages/core/src/lib/keep_alive_manager.ts @@ -1,14 +1,18 @@ import type { PeerId } from "@libp2p/interface"; import type { IRelay, Libp2p, PeerIdStr } from "@waku/interfaces"; -import type { KeepAliveOptions } from "@waku/interfaces"; import { Logger, pubsubTopicToSingleShardInfo } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; import { createEncoder } from "./message/version_0.js"; -export const RelayPingContentTopic = "/relay-ping/1/ping/null"; +const RelayPingContentTopic = "/relay-ping/1/ping/null"; const log = new Logger("keep-alive"); +type KeepAliveOptions = { + pingKeepAlive: number; + relayKeepAlive: number; +}; + type CreateKeepAliveManagerOptions = { options: KeepAliveOptions; libp2p: Libp2p; diff --git a/packages/interfaces/src/connection_manager.ts b/packages/interfaces/src/connection_manager.ts index 1644f75140..f0810e4736 100644 --- a/packages/interfaces/src/connection_manager.ts +++ b/packages/interfaces/src/connection_manager.ts @@ -8,22 +8,44 @@ export enum Tags { LOCAL = "local-peer-cache" } -export interface ConnectionManagerOptions { +export type ConnectionManagerOptions = { /** - * Number of attempts before a peer is considered non-dialable - * This is used to not spam a peer with dial attempts when it is not dialable + * Number of attempts before a peer is considered non-dialable. + * This is used to not spam a peer with dial attempts when it is not dialable. + * + * @default 3 */ maxDialAttemptsForPeer: number; + /** - * Max number of bootstrap peers allowed to be connected to, initially - * This is used to increase intention of dialing non-bootstrap peers, found using other discovery mechanisms (like Peer Exchange) + * Max number of bootstrap peers allowed to be connected to initially. + * This is used to increase intention of dialing non-bootstrap peers, found using other discovery mechanisms (like Peer Exchange). + * + * @default 1 */ maxBootstrapPeersAllowed: number; + /** - * Max number of parallel dials allowed + * Max number of parallel dials allowed. + * + * @default 3 */ maxParallelDials: number; -} + + /** + * Keep alive libp2p pings interval in seconds. + * + * @default 300 seconds + */ + pingKeepAlive: number; + + /** + * Gossip sub specific keep alive interval in seconds. + * + * @default 300 seconds + */ + relayKeepAlive: number; +}; export enum EPeersByDiscoveryEvents { PEER_DISCOVERY_BOOTSTRAP = "peer:discovery:bootstrap", diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts index 8cfe38114f..ed2cb5240a 100644 --- a/packages/interfaces/src/index.ts +++ b/packages/interfaces/src/index.ts @@ -12,7 +12,6 @@ export * from "./sender.js"; export * from "./receiver.js"; export * from "./misc.js"; export * from "./libp2p.js"; -export * from "./keep_alive_manager.js"; export * from "./dns_discovery.js"; export * from "./metadata.js"; export * from "./constants.js"; diff --git a/packages/interfaces/src/keep_alive_manager.ts b/packages/interfaces/src/keep_alive_manager.ts deleted file mode 100644 index f6d7791d10..0000000000 --- a/packages/interfaces/src/keep_alive_manager.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface KeepAliveOptions { - pingKeepAlive: number; - relayKeepAlive: number; -} diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts index 74462efa13..e18ca6592a 100644 --- a/packages/interfaces/src/protocols.ts +++ b/packages/interfaces/src/protocols.ts @@ -2,6 +2,7 @@ import type { Libp2p } from "@libp2p/interface"; import type { PeerId } from "@libp2p/interface"; import type { Peer } from "@libp2p/interface"; +import type { ConnectionManagerOptions } from "./connection_manager.js"; import type { CreateLibp2pOptions } from "./libp2p.js"; import type { IDecodedMessage } from "./message.js"; import { ThisAndThat, ThisOrThat } from "./misc.js"; @@ -58,6 +59,7 @@ export type ProtocolCreateOptions = { * See [Waku v2 Topic Usage Recommendations](https://github.com/vacp2p/rfc-index/blob/main/waku/informational/23/topics.md#content-topics) for details. * You cannot add or remove content topics after initialization of the node. */ + /** * Configuration for determining the network in use. * Network configuration refers to the shards and clusters used in the network. @@ -76,6 +78,7 @@ export type ProtocolCreateOptions = { * @default { clusterId: 1, shards: [0, 1, 2, 3, 4, 5, 6, 7] } */ networkConfig?: NetworkConfig; + /** * You can pass options to the `Libp2p` instance used by {@link @waku/sdk!WakuNode} using the `libp2p` property. * This property is the same type as the one passed to [`Libp2p.create`](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#create) @@ -84,28 +87,38 @@ export type ProtocolCreateOptions = { * Notes that some values are overridden by {@link @waku/sdk!WakuNode} to ensure it implements the Waku protocol. */ libp2p?: Partial; + /** * Number of peers to connect to, for the usage of the protocol. * This is used by: * - Light Push to send messages, * - Filter to retrieve messages. - * Defaults to 2. + * + * @default 2. */ numPeersToUse?: number; + /** * Byte array used as key for the noise protocol used for connection encryption * by [`Libp2p.create`](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#create) * This is only used for test purposes to not run out of entropy during CI runs. */ staticNoiseKey?: Uint8Array; + /** * Use recommended bootstrap method to discovery and connect to new nodes. */ defaultBootstrap?: boolean; + /** * List of peers to use to bootstrap the node. Ignored if defaultBootstrap is set to true. */ bootstrapPeers?: string[]; + + /** + * Configuration for connection manager. If not specified - default values are applied. + */ + connectionManager?: Partial; }; export type Callback = ( diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index b97244950d..0f492857b0 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -23,28 +23,12 @@ import { ReliabilityMonitorManager } from "../reliability_monitor/index.js"; import { waitForRemotePeer } from "./wait_for_remote_peer.js"; -export const DefaultPingKeepAliveValueSecs = 5 * 60; -export const DefaultRelayKeepAliveValueSecs = 5 * 60; export const DefaultUserAgent = "js-waku"; export const DefaultPingMaxInboundStreams = 10; const log = new Logger("waku"); export interface WakuOptions { - /** - * Set keep alive frequency in seconds: Waku will send a `/ipfs/ping/1.0.0` - * request to each peer after the set number of seconds. Set to 0 to disable. - * - * @default {@link @waku/core.DefaultPingKeepAliveValueSecs} - */ - pingKeepAlive?: number; - /** - * Set keep alive frequency in seconds: Waku will send a ping message over - * relay to each peer after the set number of seconds. Set to 0 to disable. - * - * @default {@link @waku/core.DefaultRelayKeepAliveValueSecs} - */ - relayKeepAlive?: number; /** * Set the user agent string to be used in identification of the node. * @default {@link @waku/core.DefaultUserAgent} @@ -87,19 +71,13 @@ export class WakuNode implements IWaku { ...protocolsEnabled }; - const pingKeepAlive = - options.pingKeepAlive || DefaultPingKeepAliveValueSecs; - const relayKeepAlive = this.relay - ? options.relayKeepAlive || DefaultRelayKeepAliveValueSecs - : 0; - const peerId = this.libp2p.peerId.toString(); this.connectionManager = new ConnectionManager({ libp2p, - keepAliveOptions: { pingKeepAlive, relayKeepAlive }, + relay: this.relay, pubsubTopics: this.pubsubTopics, - relay: this.relay + config: options?.connectionManager }); this.health = getHealthManager(); From e897d5cd6f69e9fcfa080c1cf02639f3229cf03f Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 20 Oct 2024 16:02:12 +0200 Subject: [PATCH 04/22] remove log param from peerManager --- packages/sdk/src/protocols/base_protocol.ts | 2 +- packages/sdk/src/protocols/peer_manager.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/protocols/base_protocol.ts b/packages/sdk/src/protocols/base_protocol.ts index 3ef5cc1df8..72295b2747 100644 --- a/packages/sdk/src/protocols/base_protocol.ts +++ b/packages/sdk/src/protocols/base_protocol.ts @@ -33,7 +33,7 @@ export class BaseProtocolSDK implements IBaseProtocolSDK { const maintainPeersInterval = options?.maintainPeersInterval ?? DEFAULT_MAINTAIN_PEERS_INTERVAL; - this.peerManager = new PeerManager(connectionManager, core, this.log); + this.peerManager = new PeerManager(connectionManager, core); this.log.info( `Initializing BaseProtocolSDK with numPeersToUse: ${this.numPeersToUse}, maintainPeersInterval: ${maintainPeersInterval}ms` diff --git a/packages/sdk/src/protocols/peer_manager.ts b/packages/sdk/src/protocols/peer_manager.ts index dcdc024f1b..422f508e15 100644 --- a/packages/sdk/src/protocols/peer_manager.ts +++ b/packages/sdk/src/protocols/peer_manager.ts @@ -5,6 +5,8 @@ import { IHealthManager } from "@waku/interfaces"; import { Logger } from "@waku/utils"; import { Mutex } from "async-mutex"; +const log = new Logger("peer-manager"); + export class PeerManager { private peers: Map = new Map(); private healthManager: IHealthManager; @@ -15,8 +17,7 @@ export class PeerManager { public constructor( private readonly connectionManager: ConnectionManager, - private readonly core: BaseProtocol, - private readonly log: Logger + private readonly core: BaseProtocol ) { this.healthManager = getHealthManager(); this.healthManager.updateProtocolHealth(this.core.multicodec, 0); @@ -35,7 +36,7 @@ export class PeerManager { this.writeLockHolder = `addPeer: ${peer.id.toString()}`; await this.connectionManager.attemptDial(peer.id); this.peers.set(peer.id.toString(), peer); - this.log.info(`Added and dialed peer: ${peer.id.toString()}`); + log.info(`Added and dialed peer: ${peer.id.toString()}`); this.healthManager.updateProtocolHealth( this.core.multicodec, this.peers.size @@ -48,7 +49,7 @@ export class PeerManager { return this.writeMutex.runExclusive(() => { this.writeLockHolder = `removePeer: ${peerId.toString()}`; this.peers.delete(peerId.toString()); - this.log.info(`Removed peer: ${peerId.toString()}`); + log.info(`Removed peer: ${peerId.toString()}`); this.healthManager.updateProtocolHealth( this.core.multicodec, this.peers.size @@ -66,7 +67,7 @@ export class PeerManager { } public async removeExcessPeers(excessPeers: number): Promise { - this.log.info(`Removing ${excessPeers} excess peer(s)`); + log.info(`Removing ${excessPeers} excess peer(s)`); const peersToRemove = Array.from(this.peers.values()).slice(0, excessPeers); for (const peer of peersToRemove) { await this.removePeer(peer.id); @@ -80,7 +81,7 @@ export class PeerManager { public async findAndAddPeers(numPeers: number): Promise { const additionalPeers = await this.findPeers(numPeers); if (additionalPeers.length === 0) { - this.log.warn("No additional peers found"); + log.warn("No additional peers found"); return []; } return this.addMultiplePeers(additionalPeers); From e1813bc47b9f70124e7b4ec76cccb4596bbfd1c1 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sun, 20 Oct 2024 22:54:36 +0200 Subject: [PATCH 05/22] make PeerManager use only ConnectionManager, move getPeers to ConnectionManager, remove not needed code --- packages/core/src/index.ts | 3 +- packages/core/src/lib/base_protocol.ts | 55 +-- .../connection_manager.ts | 24 ++ .../core/src/lib/connection_manager/index.ts | 1 + .../keep_alive_manager.ts | 2 +- .../core/src/lib/connection_manager/utils.ts | 25 ++ packages/core/src/lib/filter/index.ts | 2 +- packages/core/src/lib/filterPeers.spec.ts | 144 -------- packages/core/src/lib/filterPeers.ts | 51 --- packages/core/src/lib/light_push/index.ts | 2 +- packages/core/src/lib/metadata/index.ts | 2 +- packages/core/src/lib/store/index.ts | 2 +- .../src/peer-exchange/waku_peer_exchange.ts | 2 +- packages/interfaces/src/connection_manager.ts | 1 + packages/sdk/src/protocols/base_protocol.ts | 2 +- .../src/protocols/light_push/light_push.ts | 27 +- packages/sdk/src/protocols/peer_manager.ts | 23 +- packages/sdk/src/protocols/store/index.ts | 11 +- packages/tests/tests/getPeers.spec.ts | 339 ------------------ packages/utils/src/libp2p/index.ts | 38 -- 20 files changed, 73 insertions(+), 683 deletions(-) rename packages/core/src/lib/{ => connection_manager}/connection_manager.ts (96%) create mode 100644 packages/core/src/lib/connection_manager/index.ts rename packages/core/src/lib/{ => connection_manager}/keep_alive_manager.ts (98%) create mode 100644 packages/core/src/lib/connection_manager/utils.ts delete mode 100644 packages/core/src/lib/filterPeers.spec.ts delete mode 100644 packages/core/src/lib/filterPeers.ts delete mode 100644 packages/tests/tests/getPeers.spec.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9b4acf2eae..cd9f894730 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,11 +15,10 @@ export { LightPushCodec, LightPushCore } from "./lib/light_push/index.js"; export * as waku_store from "./lib/store/index.js"; export { StoreCore, StoreCodec } from "./lib/store/index.js"; -export { ConnectionManager } from "./lib/connection_manager.js"; +export { ConnectionManager } from "./lib/connection_manager/index.js"; export { getHealthManager } from "./lib/health_manager.js"; -export { KeepAliveManager } from "./lib/keep_alive_manager.js"; export { StreamManager } from "./lib/stream_manager/index.js"; export { MetadataCodec, wakuMetadata } from "./lib/metadata/index.js"; diff --git a/packages/core/src/lib/base_protocol.ts b/packages/core/src/lib/base_protocol.ts index 740298ae0a..b8ed44e8d0 100644 --- a/packages/core/src/lib/base_protocol.ts +++ b/packages/core/src/lib/base_protocol.ts @@ -5,10 +5,8 @@ import type { Libp2pComponents, PubsubTopic } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; -import { getPeersForProtocol, sortPeersByLatency } from "@waku/utils/libp2p"; +import { getPeersForProtocol } from "@waku/utils/libp2p"; -import { filterPeersByDiscovery } from "./filterPeers.js"; import { StreamManager } from "./stream_manager/index.js"; /** @@ -23,7 +21,6 @@ export class BaseProtocol implements IBaseProtocolCore { protected constructor( public multicodec: string, protected components: Libp2pComponents, - private log: Logger, public readonly pubsubTopics: PubsubTopic[] ) { this.addLibp2pEventListener = components.events.addEventListener.bind( @@ -64,54 +61,4 @@ export class BaseProtocol implements IBaseProtocolCore { return connections.length > 0; }); } - - /** - * Retrieves a list of connected peers that support the protocol. The list is sorted by latency. - * - * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned. - * @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve. - * @returns A list of peers that support the protocol sorted by latency. By default, returns all peers available, including bootstrap. - */ - public async getPeers( - { - numPeers, - maxBootstrapPeers - }: { - numPeers: number; - maxBootstrapPeers: number; - } = { - maxBootstrapPeers: 0, - numPeers: 0 - } - ): Promise { - // Retrieve all connected peers that support the protocol & shard (if configured) - const allAvailableConnectedPeers = await this.connectedPeers(); - - // Filter the peers based on discovery & number of peers requested - const filteredPeers = filterPeersByDiscovery( - allAvailableConnectedPeers, - numPeers, - maxBootstrapPeers - ); - - // Sort the peers by latency - const sortedFilteredPeers = await sortPeersByLatency( - this.components.peerStore, - filteredPeers - ); - - if (sortedFilteredPeers.length === 0) { - this.log.warn( - "No peers found. Ensure you have a connection to the network." - ); - } - - if (sortedFilteredPeers.length < numPeers) { - this.log.warn( - `Only ${sortedFilteredPeers.length} peers found. Requested ${numPeers}.` - ); - } - - return sortedFilteredPeers; - } } diff --git a/packages/core/src/lib/connection_manager.ts b/packages/core/src/lib/connection_manager/connection_manager.ts similarity index 96% rename from packages/core/src/lib/connection_manager.ts rename to packages/core/src/lib/connection_manager/connection_manager.ts index d8697784f9..4f482d4822 100644 --- a/packages/core/src/lib/connection_manager.ts +++ b/packages/core/src/lib/connection_manager/connection_manager.ts @@ -19,6 +19,7 @@ import { decodeRelayShard, shardInfoToPubsubTopics } from "@waku/utils"; import { Logger } from "@waku/utils"; import { KeepAliveManager } from "./keep_alive_manager.js"; +import { getPeerPing } from "./utils.js"; const log = new Logger("connection-manager"); @@ -180,6 +181,29 @@ export class ConnectionManager ); } + public async getConnectedPeers(codec?: string): Promise { + const peerIDs = this.libp2p.getPeers(); + + if (peerIDs.length === 0) { + return []; + } + + const peers = await Promise.all( + peerIDs.map(async (id) => { + try { + return await this.libp2p.peerStore.get(id); + } catch (e) { + return null; + } + }) + ); + + return peers + .filter((p) => !!p) + .filter((p) => (codec ? (p as Peer).protocols.includes(codec) : true)) + .sort((left, right) => getPeerPing(left) - getPeerPing(right)) as Peer[]; + } + private async dialPeerStorePeers(): Promise { const peerInfos = await this.libp2p.peerStore.all(); const dialPromises = []; diff --git a/packages/core/src/lib/connection_manager/index.ts b/packages/core/src/lib/connection_manager/index.ts new file mode 100644 index 0000000000..b5e0c20d00 --- /dev/null +++ b/packages/core/src/lib/connection_manager/index.ts @@ -0,0 +1 @@ +export { ConnectionManager } from "./connection_manager.js"; diff --git a/packages/core/src/lib/keep_alive_manager.ts b/packages/core/src/lib/connection_manager/keep_alive_manager.ts similarity index 98% rename from packages/core/src/lib/keep_alive_manager.ts rename to packages/core/src/lib/connection_manager/keep_alive_manager.ts index 3f606acd9b..266a94da39 100644 --- a/packages/core/src/lib/keep_alive_manager.ts +++ b/packages/core/src/lib/connection_manager/keep_alive_manager.ts @@ -3,7 +3,7 @@ import type { IRelay, Libp2p, PeerIdStr } from "@waku/interfaces"; import { Logger, pubsubTopicToSingleShardInfo } from "@waku/utils"; import { utf8ToBytes } from "@waku/utils/bytes"; -import { createEncoder } from "./message/version_0.js"; +import { createEncoder } from "../message/version_0.js"; const RelayPingContentTopic = "/relay-ping/1/ping/null"; const log = new Logger("keep-alive"); diff --git a/packages/core/src/lib/connection_manager/utils.ts b/packages/core/src/lib/connection_manager/utils.ts new file mode 100644 index 0000000000..b994e1df7c --- /dev/null +++ b/packages/core/src/lib/connection_manager/utils.ts @@ -0,0 +1,25 @@ +import type { Peer } from "@libp2p/interface"; +import { bytesToUtf8 } from "@waku/utils/bytes"; + +/** + * Reads peer's metadata and retrieves ping value. + * @param peer Peer or null + * @returns -1 if no ping attached, otherwise returns ping value + */ +export const getPeerPing = (peer: Peer | null): number => { + if (!peer) { + return -1; + } + + try { + const bytes = peer.metadata.get("ping"); + + if (!bytes) { + return -1; + } + + return Number(bytesToUtf8(bytes)); + } catch (e) { + return -1; + } +}; diff --git a/packages/core/src/lib/filter/index.ts b/packages/core/src/lib/filter/index.ts index 06e57cfe1c..09bbef9e62 100644 --- a/packages/core/src/lib/filter/index.ts +++ b/packages/core/src/lib/filter/index.ts @@ -40,7 +40,7 @@ export class FilterCore extends BaseProtocol implements IBaseProtocolCore { public readonly pubsubTopics: PubsubTopic[], libp2p: Libp2p ) { - super(FilterCodecs.SUBSCRIBE, libp2p.components, log, pubsubTopics); + super(FilterCodecs.SUBSCRIBE, libp2p.components, pubsubTopics); libp2p .handle(FilterCodecs.PUSH, this.onRequest.bind(this), { diff --git a/packages/core/src/lib/filterPeers.spec.ts b/packages/core/src/lib/filterPeers.spec.ts deleted file mode 100644 index 8ae77c3bd2..0000000000 --- a/packages/core/src/lib/filterPeers.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { Peer } from "@libp2p/interface"; -import type { Tag } from "@libp2p/interface"; -import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; -import { Tags } from "@waku/interfaces"; -import { expect } from "chai"; - -import { filterPeersByDiscovery } from "./filterPeers.js"; - -describe("filterPeersByDiscovery function", function () { - it("should return all peers when numPeers is 0", async function () { - const peer1 = await createSecp256k1PeerId(); - const peer2 = await createSecp256k1PeerId(); - const peer3 = await createSecp256k1PeerId(); - - const mockPeers = [ - { - id: peer1, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer2, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer3, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - } - ] as unknown as Peer[]; - - const result = filterPeersByDiscovery(mockPeers, 0, 10); - expect(result.length).to.deep.equal(mockPeers.length); - }); - - it("should return all non-bootstrap peers and no bootstrap peer when numPeers is 0 and maxBootstrapPeers is 0", async function () { - const peer1 = await createSecp256k1PeerId(); - const peer2 = await createSecp256k1PeerId(); - const peer3 = await createSecp256k1PeerId(); - const peer4 = await createSecp256k1PeerId(); - - const mockPeers = [ - { - id: peer1, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer2, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer3, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - }, - { - id: peer4, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - } - ] as unknown as Peer[]; - - const result = filterPeersByDiscovery(mockPeers, 0, 0); - - // result should have no bootstrap peers, and a total of 2 peers - expect(result.length).to.equal(2); - expect( - result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length - ).to.equal(0); - }); - - it("should return one bootstrap peer, and all non-boostrap peers, when numPeers is 0 & maxBootstrap is 1", async function () { - const peer1 = await createSecp256k1PeerId(); - const peer2 = await createSecp256k1PeerId(); - const peer3 = await createSecp256k1PeerId(); - const peer4 = await createSecp256k1PeerId(); - const peer5 = await createSecp256k1PeerId(); - - const mockPeers = [ - { - id: peer1, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer2, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer3, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - }, - { - id: peer4, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - }, - { - id: peer5, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - } - ] as unknown as Peer[]; - - const result = filterPeersByDiscovery(mockPeers, 0, 1); - - // result should have 1 bootstrap peers, and a total of 4 peers - expect(result.length).to.equal(4); - expect( - result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length - ).to.equal(1); - }); - - it("should return only bootstrap peers up to maxBootstrapPeers", async function () { - const peer1 = await createSecp256k1PeerId(); - const peer2 = await createSecp256k1PeerId(); - const peer3 = await createSecp256k1PeerId(); - const peer4 = await createSecp256k1PeerId(); - const peer5 = await createSecp256k1PeerId(); - - const mockPeers = [ - { - id: peer1, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer2, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer3, - tags: new Map([[Tags.BOOTSTRAP, { value: 100 }]]) - }, - { - id: peer4, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - }, - { - id: peer5, - tags: new Map([[Tags.PEER_EXCHANGE, { value: 100 }]]) - } - ] as unknown as Peer[]; - - const result = filterPeersByDiscovery(mockPeers, 5, 2); - - // check that result has at least 2 bootstrap peers and no more than 5 peers - expect(result.length).to.be.at.least(2); - expect(result.length).to.be.at.most(5); - expect(result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length); - }); -}); diff --git a/packages/core/src/lib/filterPeers.ts b/packages/core/src/lib/filterPeers.ts deleted file mode 100644 index 816c3bd5b5..0000000000 --- a/packages/core/src/lib/filterPeers.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Peer } from "@libp2p/interface"; -import { Tags } from "@waku/interfaces"; - -/** - * Retrieves a list of peers based on the specified criteria: - * 1. If numPeers is 0, return all peers - * 2. Bootstrap peers are prioritized - * 3. Non-bootstrap peers are randomly selected to fill up to numPeers - * - * @param peers - The list of peers to filter from. - * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned, irrespective of `maxBootstrapPeers`. - * @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve. - * @returns An array of peers based on the specified criteria. - */ -export function filterPeersByDiscovery( - peers: Peer[], - numPeers: number, - maxBootstrapPeers: number -): Peer[] { - // Collect the bootstrap peers up to the specified maximum - let bootstrapPeers = peers - .filter((peer) => peer.tags.has(Tags.BOOTSTRAP)) - .slice(0, maxBootstrapPeers); - - // If numPeers is less than the number of bootstrap peers, adjust the bootstrapPeers array - if (numPeers > 0 && numPeers < bootstrapPeers.length) { - bootstrapPeers = bootstrapPeers.slice(0, numPeers); - } - - // Collect non-bootstrap peers - const nonBootstrapPeers = peers.filter( - (peer) => !peer.tags.has(Tags.BOOTSTRAP) - ); - - // If numPeers is 0, return all peers - if (numPeers === 0) { - return [...bootstrapPeers, ...nonBootstrapPeers]; - } - - // Initialize the list of selected peers with the bootstrap peers - const selectedPeers: Peer[] = [...bootstrapPeers]; - - // Fill up to numPeers with remaining random peers if needed - while (selectedPeers.length < numPeers && nonBootstrapPeers.length > 0) { - const randomIndex = Math.floor(Math.random() * nonBootstrapPeers.length); - const randomPeer = nonBootstrapPeers.splice(randomIndex, 1)[0]; - selectedPeers.push(randomPeer); - } - - return selectedPeers; -} diff --git a/packages/core/src/lib/light_push/index.ts b/packages/core/src/lib/light_push/index.ts index e807bee5ad..440201464f 100644 --- a/packages/core/src/lib/light_push/index.ts +++ b/packages/core/src/lib/light_push/index.ts @@ -37,7 +37,7 @@ export class LightPushCore extends BaseProtocol implements IBaseProtocolCore { public readonly pubsubTopics: PubsubTopic[], libp2p: Libp2p ) { - super(LightPushCodec, libp2p.components, log, pubsubTopics); + super(LightPushCodec, libp2p.components, pubsubTopics); } private async preparePushMessage( diff --git a/packages/core/src/lib/metadata/index.ts b/packages/core/src/lib/metadata/index.ts index 1744450c7b..2b3bfe2532 100644 --- a/packages/core/src/lib/metadata/index.ts +++ b/packages/core/src/lib/metadata/index.ts @@ -30,7 +30,7 @@ class Metadata extends BaseProtocol implements IMetadata { public pubsubTopics: PubsubTopic[], libp2p: Libp2pComponents ) { - super(MetadataCodec, libp2p.components, log, pubsubTopics); + super(MetadataCodec, libp2p.components, pubsubTopics); this.libp2pComponents = libp2p; void libp2p.registrar.handle(MetadataCodec, (streamData) => { void this.onRequest(streamData); diff --git a/packages/core/src/lib/store/index.ts b/packages/core/src/lib/store/index.ts index a4b8b9c2e7..830a7b5100 100644 --- a/packages/core/src/lib/store/index.ts +++ b/packages/core/src/lib/store/index.ts @@ -32,7 +32,7 @@ export class StoreCore extends BaseProtocol implements IStoreCore { public readonly pubsubTopics: PubsubTopic[], libp2p: Libp2p ) { - super(StoreCodec, libp2p.components, log, pubsubTopics); + super(StoreCodec, libp2p.components, pubsubTopics); } public async *queryPerPage( diff --git a/packages/discovery/src/peer-exchange/waku_peer_exchange.ts b/packages/discovery/src/peer-exchange/waku_peer_exchange.ts index 6030b86f76..43520e9bde 100644 --- a/packages/discovery/src/peer-exchange/waku_peer_exchange.ts +++ b/packages/discovery/src/peer-exchange/waku_peer_exchange.ts @@ -32,7 +32,7 @@ export class WakuPeerExchange extends BaseProtocol implements IPeerExchange { components: Libp2pComponents, pubsubTopics: PubsubTopic[] ) { - super(PeerExchangeCodec, components, log, pubsubTopics); + super(PeerExchangeCodec, components, pubsubTopics); } /** diff --git a/packages/interfaces/src/connection_manager.ts b/packages/interfaces/src/connection_manager.ts index f0810e4736..8610acab95 100644 --- a/packages/interfaces/src/connection_manager.ts +++ b/packages/interfaces/src/connection_manager.ts @@ -86,6 +86,7 @@ export interface IConnectionStateEvents { export interface IConnectionManager extends TypedEventEmitter { pubsubTopics: PubsubTopic[]; + getConnectedPeers(codec?: string): Promise; dropConnection(peerId: PeerId): Promise; getPeersByDiscovery(): Promise; stop(): void; diff --git a/packages/sdk/src/protocols/base_protocol.ts b/packages/sdk/src/protocols/base_protocol.ts index 72295b2747..3a7abc1b36 100644 --- a/packages/sdk/src/protocols/base_protocol.ts +++ b/packages/sdk/src/protocols/base_protocol.ts @@ -33,7 +33,7 @@ export class BaseProtocolSDK implements IBaseProtocolSDK { const maintainPeersInterval = options?.maintainPeersInterval ?? DEFAULT_MAINTAIN_PEERS_INTERVAL; - this.peerManager = new PeerManager(connectionManager, core); + this.peerManager = new PeerManager(connectionManager); this.log.info( `Initializing BaseProtocolSDK with numPeersToUse: ${this.numPeersToUse}, maintainPeersInterval: ${maintainPeersInterval}ms` diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/protocols/light_push/light_push.ts index 5919822656..478e683b7d 100644 --- a/packages/sdk/src/protocols/light_push/light_push.ts +++ b/packages/sdk/src/protocols/light_push/light_push.ts @@ -36,8 +36,8 @@ export class LightPush implements ILightPush { public readonly protocol: LightPushCore; public constructor( - connectionManager: ConnectionManager, - private libp2p: Libp2p, + private connectionManager: ConnectionManager, + libp2p: Libp2p, options?: ProtocolCreateOptions ) { this.numPeersToUse = options?.numPeersToUse ?? DEFAULT_NUM_PEERS_TO_USE; @@ -147,26 +147,9 @@ export class LightPush implements ILightPush { } private async getConnectedPeers(): Promise { - const peerIDs = this.libp2p.getPeers(); - - if (peerIDs.length === 0) { - return []; - } - - const peers = await Promise.all( - peerIDs.map(async (id) => { - try { - return await this.libp2p.peerStore.get(id); - } catch (e) { - return null; - } - }) - ); - - return peers - .filter((p) => !!p) - .filter((p) => (p as Peer).protocols.includes(LightPushCodec)) - .slice(0, this.numPeersToUse) as Peer[]; + const peers = + await this.connectionManager.getConnectedPeers(LightPushCodec); + return peers.slice(0, this.numPeersToUse); } } diff --git a/packages/sdk/src/protocols/peer_manager.ts b/packages/sdk/src/protocols/peer_manager.ts index 422f508e15..d8bf25f0bc 100644 --- a/packages/sdk/src/protocols/peer_manager.ts +++ b/packages/sdk/src/protocols/peer_manager.ts @@ -1,7 +1,5 @@ import { Peer, PeerId } from "@libp2p/interface"; -import { ConnectionManager, getHealthManager } from "@waku/core"; -import { BaseProtocol } from "@waku/core/lib/base_protocol"; -import { IHealthManager } from "@waku/interfaces"; +import { ConnectionManager } from "@waku/core"; import { Logger } from "@waku/utils"; import { Mutex } from "async-mutex"; @@ -9,19 +7,12 @@ const log = new Logger("peer-manager"); export class PeerManager { private peers: Map = new Map(); - private healthManager: IHealthManager; private readMutex = new Mutex(); private writeMutex = new Mutex(); private writeLockHolder: string | null = null; - public constructor( - private readonly connectionManager: ConnectionManager, - private readonly core: BaseProtocol - ) { - this.healthManager = getHealthManager(); - this.healthManager.updateProtocolHealth(this.core.multicodec, 0); - } + public constructor(private readonly connectionManager: ConnectionManager) {} public getWriteLockHolder(): string | null { return this.writeLockHolder; @@ -37,10 +28,6 @@ export class PeerManager { await this.connectionManager.attemptDial(peer.id); this.peers.set(peer.id.toString(), peer); log.info(`Added and dialed peer: ${peer.id.toString()}`); - this.healthManager.updateProtocolHealth( - this.core.multicodec, - this.peers.size - ); this.writeLockHolder = null; }); } @@ -50,10 +37,6 @@ export class PeerManager { this.writeLockHolder = `removePeer: ${peerId.toString()}`; this.peers.delete(peerId.toString()); log.info(`Removed peer: ${peerId.toString()}`); - this.healthManager.updateProtocolHealth( - this.core.multicodec, - this.peers.size - ); this.writeLockHolder = null; }); } @@ -92,7 +75,7 @@ export class PeerManager { * @param numPeers The number of peers to find. */ public async findPeers(numPeers: number): Promise { - const connectedPeers = await this.core.getPeers(); + const connectedPeers = await this.connectionManager.getConnectedPeers(); return this.readMutex.runExclusive(async () => { const newPeers = connectedPeers diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index 322e34f6d3..efd58033f8 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -58,12 +58,11 @@ export class Store extends BaseProtocolSDK implements IStore { ...options }; - const peer = ( - await this.protocol.getPeers({ - numPeers: this.numPeersToUse, - maxBootstrapPeers: 1 - }) - )[0]; + const peers = await this.connectionManager.getConnectedPeers( + this.core.multicodec + ); + const peer = peers[0]; + if (!peer) { log.error("No peers available to query"); throw new Error("No peers available to query"); diff --git a/packages/tests/tests/getPeers.spec.ts b/packages/tests/tests/getPeers.spec.ts deleted file mode 100644 index dd0ba3ad5c..0000000000 --- a/packages/tests/tests/getPeers.spec.ts +++ /dev/null @@ -1,339 +0,0 @@ -import type { Connection, Peer, PeerStore } from "@libp2p/interface"; -import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; -import { - createLightNode, - Libp2pComponents, - type LightNode, - Tags, - utf8ToBytes -} from "@waku/sdk"; -import { encodeRelayShard } from "@waku/utils"; -import { expect } from "chai"; -import fc from "fast-check"; -import Sinon from "sinon"; - -import { - afterEachCustom, - beforeEachCustom, - DefaultTestShardInfo -} from "../src/index.js"; - -describe("getPeers", function () { - let peerStore: PeerStore; - let connectionManager: Libp2pComponents["connectionManager"]; - let waku: LightNode; - const lowPingBytes = utf8ToBytes("50"); - const midPingBytes = utf8ToBytes("100"); - const highPingBytes = utf8ToBytes("200"); - - let lowPingBootstrapPeer: Peer, - lowPingNonBootstrapPeer: Peer, - midPingBootstrapPeer: Peer, - midPingNonBootstrapPeer: Peer, - highPingBootstrapPeer: Peer, - highPingNonBootstrapPeer: Peer, - differentCodecPeer: Peer, - anotherDifferentCodecPeer: Peer; - - let bootstrapPeers: Peer[]; - let nonBootstrapPeers: Peer[]; - let allPeers: Peer[]; - - beforeEachCustom(this, async () => { - waku = await createLightNode({ networkConfig: DefaultTestShardInfo }); - peerStore = waku.libp2p.peerStore; - connectionManager = waku.libp2p.components.connectionManager; - - const [ - lowPingBootstrapPeerId, - lowPingNonBootstrapPeerId, - midPingBootstrapPeerId, - midPingNonBootstrapPeerId, - highPingBootstrapPeerId, - highPingNonBootstrapPeerId, - differentCodecPeerId, - anotherDifferentCodecPeerId - ] = await Promise.all([ - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId(), - createSecp256k1PeerId() - ]); - - lowPingBootstrapPeer = { - id: lowPingBootstrapPeerId, - protocols: [waku.lightPush.protocol.multicodec], - metadata: new Map().set("ping", lowPingBytes), - tags: new Map().set(Tags.BOOTSTRAP, {}) - } as Peer; - lowPingNonBootstrapPeer = { - id: lowPingNonBootstrapPeerId, - protocols: [waku.lightPush.protocol.multicodec], - metadata: new Map().set("ping", lowPingBytes), - tags: new Map().set(Tags.PEER_EXCHANGE, {}) - } as Peer; - midPingBootstrapPeer = { - id: midPingBootstrapPeerId, - protocols: [waku.lightPush.protocol.multicodec], - metadata: new Map().set("ping", midPingBytes), - tags: new Map().set(Tags.BOOTSTRAP, {}) - } as Peer; - midPingNonBootstrapPeer = { - id: midPingNonBootstrapPeerId, - protocols: [waku.lightPush.protocol.multicodec], - metadata: new Map().set("ping", midPingBytes), - tags: new Map().set(Tags.PEER_EXCHANGE, {}) - } as Peer; - highPingBootstrapPeer = { - id: highPingBootstrapPeerId, - protocols: [waku.lightPush.protocol.multicodec], - metadata: new Map().set("ping", highPingBytes), - tags: new Map().set(Tags.BOOTSTRAP, {}) - } as Peer; - highPingNonBootstrapPeer = { - id: highPingNonBootstrapPeerId, - protocols: [waku.lightPush.protocol.multicodec], - metadata: new Map().set("ping", highPingBytes), - tags: new Map().set(Tags.PEER_EXCHANGE, {}) - } as Peer; - differentCodecPeer = { - id: differentCodecPeerId, - protocols: ["different/1"], - metadata: new Map().set("ping", lowPingBytes), - tags: new Map().set(Tags.BOOTSTRAP, {}) - } as Peer; - anotherDifferentCodecPeer = { - id: anotherDifferentCodecPeerId, - protocols: ["different/2"], - metadata: new Map().set("ping", lowPingBytes), - tags: new Map().set(Tags.BOOTSTRAP, {}) - } as Peer; - - bootstrapPeers = [ - lowPingBootstrapPeer, - midPingBootstrapPeer, - highPingBootstrapPeer - ]; - - nonBootstrapPeers = [ - lowPingNonBootstrapPeer, - midPingNonBootstrapPeer, - highPingNonBootstrapPeer - ]; - - allPeers = [ - ...bootstrapPeers, - ...nonBootstrapPeers, - differentCodecPeer, - anotherDifferentCodecPeer - ]; - - allPeers.forEach((peer) => { - peer.metadata.set("shardInfo", encodeRelayShard(DefaultTestShardInfo)); - }); - - Sinon.stub(peerStore, "get").callsFake(async (peerId) => { - return allPeers.find((peer) => peer.id.equals(peerId))!; - }); - - Sinon.stub(peerStore, "forEach").callsFake(async (callback) => { - for (const peer of allPeers) { - callback(peer); - } - }); - - // assume all peers have an opened connection - Sinon.stub(connectionManager, "getConnections").callsFake(() => { - const connections: Connection[] = []; - for (const peer of allPeers) { - connections.push({ - status: "open", - remotePeer: peer.id, - streams: [{ protocol: waku.lightPush.protocol.multicodec }] - } as unknown as Connection); - } - return connections; - }); - }); - - afterEachCustom(this, async () => { - Sinon.restore(); - }); - - describe("getPeers with varying maxBootstrapPeers", function () { - const maxBootstrapPeersValues = [1, 2, 3, 4, 5, 6, 7]; - - maxBootstrapPeersValues.forEach((maxBootstrapPeers) => { - describe(`maxBootstrapPeers=${maxBootstrapPeers}`, function () { - it(`numPeers=1 -- returns one bootstrap peer `, async function () { - const result = (await (waku.lightPush.protocol as any).getPeers({ - numPeers: 1, - maxBootstrapPeers - })) as Peer[]; - - // Should only have 1 peer - expect(result).to.have.lengthOf(1); - - // The peer should be a bootstrap peer - expect(result[0].tags.has(Tags.BOOTSTRAP)).to.be.true; - - // Peer should be of the same protocol - expect( - result[0].protocols.includes(waku.lightPush.protocol.multicodec) - ).to.be.true; - - // Peer should have the lowest ping - expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); - }); - - it(`numPeers=2 -- returns total 2 peers, with max ${maxBootstrapPeers} bootstrap peers`, async function () { - const result = (await (waku.lightPush.protocol as any).getPeers({ - numPeers: 2, - maxBootstrapPeers - })) as Peer[]; - - // Should only have 2 peers - expect(result).to.have.lengthOf(2); - - // Should only have ${maxBootstrapPeers} bootstrap peers - expect( - result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length - ).to.be.lessThanOrEqual(maxBootstrapPeers); - - // Should return peers with the same protocol - expect( - result.every((peer: Peer) => - peer.protocols.includes(waku.lightPush.protocol.multicodec) - ) - ).to.be.true; - - // All peers should be sorted by latency - // 0th index should be the lowest ping of all peers returned - expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); - }); - - it(`numPeers=3 -- returns total 3 peers, with max ${maxBootstrapPeers} bootstrap peers`, async function () { - const result = (await (waku.lightPush.protocol as any).getPeers({ - numPeers: 3, - maxBootstrapPeers - })) as Peer[]; - - // Should only have 3 peers - expect(result).to.have.lengthOf(3); - - // Should only have ${maxBootstrapPeers} bootstrap peers - expect( - result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length - ).to.be.lessThanOrEqual(maxBootstrapPeers); - - // Should return peers with the same protocol - expect( - result.every((peer: Peer) => - peer.protocols.includes(waku.lightPush.protocol.multicodec) - ) - ).to.be.true; - - // All peers should be sorted by latency - // 0th index should be the lowest ping of all peers returned - expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); - }); - - it(`numPeers=4 -- returns total 4 peers, with max ${maxBootstrapPeers} bootstrap peers`, async function () { - const result = (await (waku.lightPush.protocol as any).getPeers({ - numPeers: 4, - maxBootstrapPeers - })) as Peer[]; - - // Should only have 4 peers - expect(result).to.have.lengthOf(4); - - // Should only have ${maxBootstrapPeers} bootstrap peers - expect( - result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length - ).to.be.lessThanOrEqual(maxBootstrapPeers); - - // Should return peers with the same protocol - expect( - result.every((peer: Peer) => - peer.protocols.includes(waku.lightPush.protocol.multicodec) - ) - ).to.be.true; - - // All peers should be sorted by latency - // 0th index should be the lowest ping of all peers returned - expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); - }); - - it(`numPeers=0 -- returns all peers including all non-bootstrap with maxBootstrapPeers: ${maxBootstrapPeers}`, async function () { - const result = (await (waku.lightPush.protocol as any).getPeers({ - numPeers: 0, - maxBootstrapPeers - })) as Peer[]; - - // Should have all non-bootstrap peers + ${maxBootstrapPeers} bootstrap peers - // Unless bootstrapPeers.length < maxBootstrapPeers - // Then it should be all non-bootstrap peers + bootstrapPeers.length - if (maxBootstrapPeers > bootstrapPeers.length) { - expect(result).to.have.lengthOf( - nonBootstrapPeers.length + bootstrapPeers.length - ); - } else { - expect(result).to.have.lengthOf( - nonBootstrapPeers.length + maxBootstrapPeers - ); - } - - // All peers should be bootstrap peers - expect( - result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length - ).to.be.lessThanOrEqual(maxBootstrapPeers); - - // Peers should be of the same protocol - expect( - result.every((peer: Peer) => - peer.protocols.includes(waku.lightPush.protocol.multicodec) - ) - ).to.be.true; - - // All peers returned should be sorted by latency - // 0th index should be the lowest ping of all peers returned - expect(result[0].metadata.get("ping")).to.equal(lowPingBytes); - }); - }); - }); - }); - - describe("getPeers property-based tests", function () { - it("should return the correct number of peers based on numPeers and maxBootstrapPeers", async function () { - await fc.assert( - fc.asyncProperty( - //max bootstrap peers - fc.integer({ min: 1, max: 100 }), - //numPeers - fc.integer({ min: 0, max: 100 }), - async (maxBootstrapPeers, numPeers) => { - const result = (await (waku.lightPush.protocol as any).getPeers({ - numPeers, - maxBootstrapPeers - })) as Peer[]; - - if (numPeers === 0) { - // Expect all peers when numPeers is 0 - expect(result.length).to.be.greaterThanOrEqual(1); - } else { - // Expect up to numPeers peers - expect(result.length).to.be.lessThanOrEqual(numPeers); - } - } - ), - { - verbose: true - } - ); - }); - }); -}); diff --git a/packages/utils/src/libp2p/index.ts b/packages/utils/src/libp2p/index.ts index 80feaec6b3..c24df61883 100644 --- a/packages/utils/src/libp2p/index.ts +++ b/packages/utils/src/libp2p/index.ts @@ -1,7 +1,5 @@ import type { Peer, PeerStore } from "@libp2p/interface"; -import { bytesToUtf8 } from "../bytes/index.js"; - /** * Returns a pseudo-random peer that supports the given protocol. * Useful for protocols such as store and light push @@ -13,42 +11,6 @@ export function selectRandomPeer(peers: Peer[]): Peer | undefined { return peers[index]; } -/** - * Function to sort peers by latency from lowest to highest - * @param peerStore - The Libp2p PeerStore - * @param peers - The list of peers to choose from - * @returns Sorted array of peers by latency - */ -export async function sortPeersByLatency( - peerStore: PeerStore, - peers: Peer[] -): Promise { - if (peers.length === 0) return []; - - const results = await Promise.all( - peers.map(async (peer) => { - try { - const pingBytes = (await peerStore.get(peer.id)).metadata.get("ping"); - if (!pingBytes) return { peer, ping: Infinity }; - - const ping = Number(bytesToUtf8(pingBytes)); - return { peer, ping }; - } catch (error) { - return { peer, ping: Infinity }; - } - }) - ); - - // filter out null values - const validResults = results.filter( - (result): result is { peer: Peer; ping: number } => result !== null - ); - - return validResults - .sort((a, b) => a.ping - b.ping) - .map((result) => result.peer); -} - /** * Returns the list of peers that supports the given protocol. */ From 9bdc2af13b16404698cc418c710d452b1ac2855a Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 21 Oct 2024 01:34:33 +0200 Subject: [PATCH 06/22] remove allPeers and connectedPeers from BaseProtocolCore, update tests, add getPeers for IWaku --- packages/core/src/lib/base_protocol.ts | 20 ---------- packages/interfaces/src/protocols.ts | 2 - packages/interfaces/src/waku.ts | 7 +++- packages/sdk/src/waku/waku.ts | 6 ++- packages/tests/tests/metadata.spec.ts | 4 +- .../tests/wait_for_remote_peer.node.spec.ts | 37 +++++-------------- packages/utils/package.json | 4 -- packages/utils/src/libp2p/index.ts | 31 ---------------- 8 files changed, 24 insertions(+), 87 deletions(-) delete mode 100644 packages/utils/src/libp2p/index.ts diff --git a/packages/core/src/lib/base_protocol.ts b/packages/core/src/lib/base_protocol.ts index b8ed44e8d0..4143056599 100644 --- a/packages/core/src/lib/base_protocol.ts +++ b/packages/core/src/lib/base_protocol.ts @@ -5,7 +5,6 @@ import type { Libp2pComponents, PubsubTopic } from "@waku/interfaces"; -import { getPeersForProtocol } from "@waku/utils/libp2p"; import { StreamManager } from "./stream_manager/index.js"; @@ -42,23 +41,4 @@ export class BaseProtocol implements IBaseProtocolCore { protected async getStream(peer: Peer): Promise { return this.streamManager.getStream(peer); } - - /** - * Returns known peers from the address book (`libp2p.peerStore`) that support - * the class protocol. Waku may or may not be currently connected to these - * peers. - */ - public async allPeers(): Promise { - return getPeersForProtocol(this.components.peerStore, [this.multicodec]); - } - - public async connectedPeers(): Promise { - const peers = await this.allPeers(); - return peers.filter((peer) => { - const connections = this.components.connectionManager.getConnections( - peer.id - ); - return connections.length > 0; - }); - } } diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts index e18ca6592a..664ed49f3c 100644 --- a/packages/interfaces/src/protocols.ts +++ b/packages/interfaces/src/protocols.ts @@ -17,8 +17,6 @@ export enum Protocols { export type IBaseProtocolCore = { multicodec: string; - allPeers: () => Promise; - connectedPeers: () => Promise; addLibp2pEventListener: Libp2p["addEventListener"]; removeLibp2pEventListener: Libp2p["removeEventListener"]; }; diff --git a/packages/interfaces/src/waku.ts b/packages/interfaces/src/waku.ts index 4f3edf4f86..733fb5093e 100644 --- a/packages/interfaces/src/waku.ts +++ b/packages/interfaces/src/waku.ts @@ -1,4 +1,4 @@ -import type { PeerId, Stream } from "@libp2p/interface"; +import type { Peer, PeerId, Stream } from "@libp2p/interface"; import type { MultiaddrInput } from "@multiformats/multiaddr"; import { IConnectionManager } from "./connection_manager.js"; @@ -121,6 +121,11 @@ export interface IWaku { * @returns {boolean} `true` if the node has working connection and `false` otherwise */ isConnected(): boolean; + + /** + * @returns {Peer[]} an array of all connected peers + */ + getPeers(): Promise; } export interface LightNode extends IWaku { diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index 0f492857b0..76bbad601c 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -1,5 +1,5 @@ import type { Stream } from "@libp2p/interface"; -import { isPeerId, PeerId } from "@libp2p/interface"; +import { isPeerId, Peer, PeerId } from "@libp2p/interface"; import { multiaddr, Multiaddr, MultiaddrInput } from "@multiformats/multiaddr"; import { ConnectionManager, getHealthManager } from "@waku/core"; import type { @@ -186,6 +186,10 @@ export class WakuNode implements IWaku { await this.libp2p.stop(); } + public async getPeers(): Promise { + return this.connectionManager.getConnectedPeers(); + } + public async waitForPeers( protocols?: Protocols[], timeoutMs?: number diff --git a/packages/tests/tests/metadata.spec.ts b/packages/tests/tests/metadata.spec.ts index 98099873e8..80625e2bb3 100644 --- a/packages/tests/tests/metadata.spec.ts +++ b/packages/tests/tests/metadata.spec.ts @@ -253,7 +253,9 @@ describe("Metadata Protocol", function () { waku = await createLightNode({ networkConfig: shardInfo, - pingKeepAlive: 1 + connectionManager: { + pingKeepAlive: 1 + } }); await waku.start(); await waku.libp2p.dialProtocol(nwaku1Ma, MetadataCodec); diff --git a/packages/tests/tests/wait_for_remote_peer.node.spec.ts b/packages/tests/tests/wait_for_remote_peer.node.spec.ts index 1b3e039c45..37b5d6e632 100644 --- a/packages/tests/tests/wait_for_remote_peer.node.spec.ts +++ b/packages/tests/tests/wait_for_remote_peer.node.spec.ts @@ -115,9 +115,7 @@ describe("Wait for remote peer", function () { await delay(1000); await waku2.waitForPeers([Protocols.Store]); - const peers = (await waku2.store.protocol.connectedPeers()).map((peer) => - peer.id.toString() - ); + const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); const nimPeerId = multiAddrWithId.getPeerId(); expect(nimPeerId).to.not.be.undefined; @@ -145,9 +143,7 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waitPromise; - const peers = (await waku2.store.protocol.connectedPeers()).map((peer) => - peer.id.toString() - ); + const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); const nimPeerId = multiAddrWithId.getPeerId(); @@ -174,9 +170,7 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waku2.waitForPeers([Protocols.LightPush]); - const peers = (await waku2.lightPush.protocol.connectedPeers()).map( - (peer) => peer.id.toString() - ); + const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); const nimPeerId = multiAddrWithId.getPeerId(); @@ -203,9 +197,7 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waku2.waitForPeers([Protocols.Filter]); - const peers = (await waku2.filter.protocol.connectedPeers()).map((peer) => - peer.id.toString() - ); + const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); const nimPeerId = multiAddrWithId.getPeerId(); @@ -213,14 +205,15 @@ describe("Wait for remote peer", function () { expect(peers.includes(nimPeerId as string)).to.be.true; }); + // TODO: re-enable store once https://github.com/waku-org/js-waku/issues/2162 is fixed it("Light Node - default protocols", async function () { this.timeout(20_000); nwaku = new ServiceNode(makeLogFileName(this)); await nwaku.start({ filter: true, lightpush: true, - relay: false, - store: true + relay: false + // store: true }); const multiAddrWithId = await nwaku.getMultiaddrWithId(); @@ -232,26 +225,16 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waku2.waitForPeers([ Protocols.Filter, - Protocols.Store, + // Protocols.Store, Protocols.LightPush ]); - const filterPeers = (await waku2.filter.protocol.connectedPeers()).map( - (peer) => peer.id.toString() - ); - const storePeers = (await waku2.store.protocol.connectedPeers()).map( - (peer) => peer.id.toString() - ); - const lightPushPeers = ( - await waku2.lightPush.protocol.connectedPeers() - ).map((peer) => peer.id.toString()); + const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); const nimPeerId = multiAddrWithId.getPeerId(); expect(nimPeerId).to.not.be.undefined; - expect(filterPeers.includes(nimPeerId as string)).to.be.true; - expect(storePeers.includes(nimPeerId as string)).to.be.true; - expect(lightPushPeers.includes(nimPeerId as string)).to.be.true; + expect(peers.includes(nimPeerId as string)).to.be.true; }); it("Privacy Node - default protocol", async function () { diff --git a/packages/utils/package.json b/packages/utils/package.json index 99d132eb1f..8538b08a8f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -9,10 +9,6 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js" }, - "./libp2p": { - "types": "./dist/libp2p/index.d.ts", - "import": "./dist/libp2p/index.js" - }, "./bytes": { "types": "./dist/bytes/index.d.ts", "import": "./dist/bytes/index.js" diff --git a/packages/utils/src/libp2p/index.ts b/packages/utils/src/libp2p/index.ts deleted file mode 100644 index c24df61883..0000000000 --- a/packages/utils/src/libp2p/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Peer, PeerStore } from "@libp2p/interface"; - -/** - * Returns a pseudo-random peer that supports the given protocol. - * Useful for protocols such as store and light push - */ -export function selectRandomPeer(peers: Peer[]): Peer | undefined { - if (peers.length === 0) return; - - const index = Math.round(Math.random() * (peers.length - 1)); - return peers[index]; -} - -/** - * Returns the list of peers that supports the given protocol. - */ -export async function getPeersForProtocol( - peerStore: PeerStore, - protocols: string[] -): Promise { - const peers: Peer[] = []; - await peerStore.forEach((peer) => { - for (let i = 0; i < protocols.length; i++) { - if (peer.protocols.includes(protocols[i])) { - peers.push(peer); - break; - } - } - }); - return peers; -} From 2edd8564345d28caac85d534f3b1fac3b597f2ea Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 22 Oct 2024 15:21:50 +0200 Subject: [PATCH 07/22] use only one peerManager from Waku object --- packages/sdk/src/protocols/base_protocol.ts | 6 +----- packages/sdk/src/protocols/filter/index.ts | 9 ++++++--- packages/sdk/src/protocols/store/index.ts | 22 +++++++++++---------- packages/sdk/src/waku/waku.ts | 8 +++++++- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/sdk/src/protocols/base_protocol.ts b/packages/sdk/src/protocols/base_protocol.ts index 3a7abc1b36..566dda9155 100644 --- a/packages/sdk/src/protocols/base_protocol.ts +++ b/packages/sdk/src/protocols/base_protocol.ts @@ -1,5 +1,4 @@ import type { Peer, PeerId } from "@libp2p/interface"; -import { ConnectionManager } from "@waku/core"; import { BaseProtocol } from "@waku/core/lib/base_protocol"; import { IBaseProtocolSDK, ProtocolUseOptions } from "@waku/interfaces"; import { Logger } from "@waku/utils"; @@ -15,7 +14,6 @@ export const DEFAULT_NUM_PEERS_TO_USE = 2; const DEFAULT_MAINTAIN_PEERS_INTERVAL = 30_000; export class BaseProtocolSDK implements IBaseProtocolSDK { - private peerManager: PeerManager; public readonly numPeersToUse: number; private maintainPeersIntervalId: ReturnType< typeof window.setInterval @@ -24,7 +22,7 @@ export class BaseProtocolSDK implements IBaseProtocolSDK { public constructor( protected core: BaseProtocol, - protected connectionManager: ConnectionManager, + protected peerManager: PeerManager, options: Options ) { this.log = new Logger(`sdk:${core.multicodec}`); @@ -33,8 +31,6 @@ export class BaseProtocolSDK implements IBaseProtocolSDK { const maintainPeersInterval = options?.maintainPeersInterval ?? DEFAULT_MAINTAIN_PEERS_INTERVAL; - this.peerManager = new PeerManager(connectionManager); - this.log.info( `Initializing BaseProtocolSDK with numPeersToUse: ${this.numPeersToUse}, maintainPeersInterval: ${maintainPeersInterval}ms` ); diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index d5509c2337..1db0ed1d37 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -26,6 +26,7 @@ import { } from "@waku/utils"; import { BaseProtocolSDK } from "../base_protocol.js"; +import { PeerManager } from "../peer_manager.js"; import { DEFAULT_SUBSCRIBE_OPTIONS } from "./constants.js"; import { SubscriptionManager } from "./subscription_manager.js"; @@ -38,8 +39,9 @@ class Filter extends BaseProtocolSDK implements IFilter { private activeSubscriptions = new Map(); public constructor( - connectionManager: ConnectionManager, + private connectionManager: ConnectionManager, private libp2p: Libp2p, + peerManager: PeerManager, private lightPush?: ILightPush, options?: ProtocolCreateOptions ) { @@ -59,7 +61,7 @@ class Filter extends BaseProtocolSDK implements IFilter { connectionManager.pubsubTopics, libp2p ), - connectionManager, + peerManager, { numPeersToUse: options?.numPeersToUse } ); @@ -304,9 +306,10 @@ class Filter extends BaseProtocolSDK implements IFilter { export function wakuFilter( connectionManager: ConnectionManager, + peerManager: PeerManager, lightPush?: ILightPush, init?: ProtocolCreateOptions ): (libp2p: Libp2p) => IFilter { return (libp2p: Libp2p) => - new Filter(connectionManager, libp2p, lightPush, init); + new Filter(connectionManager, libp2p, peerManager, lightPush, init); } diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index efd58033f8..36249a0f2f 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -11,6 +11,7 @@ import { messageHash } from "@waku/message-hash"; import { ensurePubsubTopicIsConfigured, isDefined, Logger } from "@waku/utils"; import { BaseProtocolSDK } from "../base_protocol.js"; +import { PeerManager } from "../peer_manager.js"; const DEFAULT_NUM_PEERS = 1; @@ -23,14 +24,14 @@ const log = new Logger("waku:store:sdk"); export class Store extends BaseProtocolSDK implements IStore { public readonly protocol: StoreCore; - public constructor(connectionManager: ConnectionManager, libp2p: Libp2p) { - super( - new StoreCore(connectionManager.pubsubTopics, libp2p), - connectionManager, - { - numPeersToUse: DEFAULT_NUM_PEERS - } - ); + public constructor( + private connectionManager: ConnectionManager, + libp2p: Libp2p, + peerManager: PeerManager + ) { + super(new StoreCore(connectionManager.pubsubTopics, libp2p), peerManager, { + numPeersToUse: DEFAULT_NUM_PEERS + }); this.protocol = this.core as StoreCore; } @@ -236,9 +237,10 @@ export class Store extends BaseProtocolSDK implements IStore { * @returns A function that takes a Libp2p instance and returns a StoreSDK instance. */ export function wakuStore( - connectionManager: ConnectionManager + connectionManager: ConnectionManager, + peerManager: PeerManager ): (libp2p: Libp2p) => IStore { return (libp2p: Libp2p) => { - return new Store(connectionManager, libp2p); + return new Store(connectionManager, libp2p, peerManager); }; } diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index 76bbad601c..90572896d9 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -18,6 +18,7 @@ import { Logger } from "@waku/utils"; import { wakuFilter } from "../protocols/filter/index.js"; import { wakuLightPush } from "../protocols/light_push/index.js"; +import { PeerManager } from "../protocols/peer_manager.js"; import { wakuStore } from "../protocols/store/index.js"; import { ReliabilityMonitorManager } from "../reliability_monitor/index.js"; @@ -54,6 +55,8 @@ export class WakuNode implements IWaku { public connectionManager: ConnectionManager; public readonly health: IHealthManager; + private readonly peerManager: PeerManager; + public constructor( public readonly pubsubTopics: PubsubTopic[], options: CreateWakuNodeOptions, @@ -80,10 +83,12 @@ export class WakuNode implements IWaku { config: options?.connectionManager }); + this.peerManager = new PeerManager(this.connectionManager); + this.health = getHealthManager(); if (protocolsEnabled.store) { - const store = wakuStore(this.connectionManager); + const store = wakuStore(this.connectionManager, this.peerManager); this.store = store(libp2p); } @@ -95,6 +100,7 @@ export class WakuNode implements IWaku { if (protocolsEnabled.filter) { const filter = wakuFilter( this.connectionManager, + this.peerManager, this.lightPush, options ); From 7ae3b91b98471e35dfb2190618f67fd242910a78 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 22 Oct 2024 16:02:40 +0200 Subject: [PATCH 08/22] remove IBaseProtocolSDK and merge with PeerManager --- packages/interfaces/src/filter.ts | 18 +- packages/interfaces/src/protocols.ts | 7 - packages/interfaces/src/store.ts | 4 +- packages/sdk/src/protocols/base_protocol.ts | 187 ------------------ packages/sdk/src/protocols/filter/index.ts | 58 +++--- .../protocols/light_push/light_push.spec.ts | 3 +- .../src/protocols/light_push/light_push.ts | 33 +--- packages/sdk/src/protocols/peer_manager.ts | 178 ++++++++++++++++- packages/sdk/src/protocols/store/index.ts | 18 +- packages/sdk/src/waku/waku.ts | 12 +- 10 files changed, 234 insertions(+), 284 deletions(-) delete mode 100644 packages/sdk/src/protocols/base_protocol.ts diff --git a/packages/interfaces/src/filter.ts b/packages/interfaces/src/filter.ts index bbe6bb351b..373aac5121 100644 --- a/packages/interfaces/src/filter.ts +++ b/packages/interfaces/src/filter.ts @@ -5,7 +5,6 @@ import type { ContentTopic, ThisOrThat } from "./misc.js"; import type { Callback, IBaseProtocolCore, - IBaseProtocolSDK, ProtocolError, ProtocolUseOptions, SDKProtocolResult @@ -37,15 +36,14 @@ export interface ISubscription { unsubscribeAll(): Promise; } -export type IFilter = IReceiver & - IBaseProtocolSDK & { protocol: IBaseProtocolCore } & { - subscribe( - decoders: IDecoder | IDecoder[], - callback: Callback, - protocolUseOptions?: ProtocolUseOptions, - subscribeOptions?: SubscribeOptions - ): Promise; - }; +export type IFilter = IReceiver & { protocol: IBaseProtocolCore } & { + subscribe( + decoders: IDecoder | IDecoder[], + callback: Callback, + protocolUseOptions?: ProtocolUseOptions, + subscribeOptions?: SubscribeOptions + ): Promise; +}; export type SubscribeResult = SubscriptionSuccess | SubscriptionError; diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts index 664ed49f3c..42e4708911 100644 --- a/packages/interfaces/src/protocols.ts +++ b/packages/interfaces/src/protocols.ts @@ -1,6 +1,5 @@ import type { Libp2p } from "@libp2p/interface"; import type { PeerId } from "@libp2p/interface"; -import type { Peer } from "@libp2p/interface"; import type { ConnectionManagerOptions } from "./connection_manager.js"; import type { CreateLibp2pOptions } from "./libp2p.js"; @@ -21,12 +20,6 @@ export type IBaseProtocolCore = { removeLibp2pEventListener: Libp2p["removeEventListener"]; }; -export type IBaseProtocolSDK = { - readonly connectedPeers: Peer[]; - renewPeer: (peerToDisconnect: PeerId) => Promise; - readonly numPeersToUse: number; -}; - export type NetworkConfig = StaticSharding | AutoSharding; //TODO: merge this with ProtocolCreateOptions or establish distinction: https://github.com/waku-org/js-waku/issues/2048 diff --git a/packages/interfaces/src/store.ts b/packages/interfaces/src/store.ts index 44560db62f..a613ef73ac 100644 --- a/packages/interfaces/src/store.ts +++ b/packages/interfaces/src/store.ts @@ -1,5 +1,5 @@ import type { IDecodedMessage, IDecoder } from "./message.js"; -import type { IBaseProtocolCore, IBaseProtocolSDK } from "./protocols.js"; +import type { IBaseProtocolCore } from "./protocols.js"; export type StoreCursor = Uint8Array; @@ -78,7 +78,7 @@ export type QueryRequestParams = { export type IStoreCore = IBaseProtocolCore; -export type IStore = IBaseProtocolSDK & { +export type IStore = { protocol: IBaseProtocolCore; createCursor(message: IDecodedMessage): StoreCursor; queryGenerator: ( diff --git a/packages/sdk/src/protocols/base_protocol.ts b/packages/sdk/src/protocols/base_protocol.ts deleted file mode 100644 index 566dda9155..0000000000 --- a/packages/sdk/src/protocols/base_protocol.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { Peer, PeerId } from "@libp2p/interface"; -import { BaseProtocol } from "@waku/core/lib/base_protocol"; -import { IBaseProtocolSDK, ProtocolUseOptions } from "@waku/interfaces"; -import { Logger } from "@waku/utils"; - -import { PeerManager } from "./peer_manager.js"; - -interface Options { - numPeersToUse?: number; - maintainPeersInterval?: number; -} - -export const DEFAULT_NUM_PEERS_TO_USE = 2; -const DEFAULT_MAINTAIN_PEERS_INTERVAL = 30_000; - -export class BaseProtocolSDK implements IBaseProtocolSDK { - public readonly numPeersToUse: number; - private maintainPeersIntervalId: ReturnType< - typeof window.setInterval - > | null = null; - private log: Logger; - - public constructor( - protected core: BaseProtocol, - protected peerManager: PeerManager, - options: Options - ) { - this.log = new Logger(`sdk:${core.multicodec}`); - - this.numPeersToUse = options?.numPeersToUse ?? DEFAULT_NUM_PEERS_TO_USE; - const maintainPeersInterval = - options?.maintainPeersInterval ?? DEFAULT_MAINTAIN_PEERS_INTERVAL; - - this.log.info( - `Initializing BaseProtocolSDK with numPeersToUse: ${this.numPeersToUse}, maintainPeersInterval: ${maintainPeersInterval}ms` - ); - void this.startMaintainPeersInterval(maintainPeersInterval); - } - - public get connectedPeers(): Peer[] { - return this.peerManager.getPeers().slice(0, this.numPeersToUse); - } - - /** - * Disconnects from a peer and tries to find a new one to replace it. - * @param peerToDisconnect The peer to disconnect from. - * @returns The new peer that was found and connected to. - */ - public async renewPeer(peerToDisconnect: PeerId): Promise { - this.log.info(`Attempting to renew peer ${peerToDisconnect}`); - - const newPeer = await this.peerManager.findPeers(1); - if (newPeer.length === 0) { - this.log.error( - "Failed to find a new peer to replace the disconnected one" - ); - return undefined; - } - - await this.peerManager.removePeer(peerToDisconnect); - await this.peerManager.addPeer(newPeer[0]); - - this.log.info(`Successfully renewed peer. New peer: ${newPeer[0].id}`); - - return newPeer[0]; - } - - /** - * Stops the maintain peers interval. - */ - public stopMaintainPeersInterval(): void { - if (this.maintainPeersIntervalId) { - clearInterval(this.maintainPeersIntervalId); - this.maintainPeersIntervalId = null; - this.log.info("Maintain peers interval stopped"); - } else { - this.log.info("Maintain peers interval was not running"); - } - } - - /** - * Checks if there are sufficient peers to send a message to. - * If `forceUseAllPeers` is `false` (default), returns `true` if there are any connected peers. - * If `forceUseAllPeers` is `true`, attempts to connect to `numPeersToUse` peers. - * @param options Optional options object - * @param options.forceUseAllPeers Optional flag to force connecting to `numPeersToUse` peers (default: false) - * @param options.maxAttempts Optional maximum number of attempts to reach the required number of peers (default: 3) - * @returns `true` if the required number of peers are connected, `false` otherwise - */ - protected async hasPeers( - options: Partial = {} - ): Promise { - const { forceUseAllPeers = false, maxAttempts = 3 } = options; - - this.log.info( - `Checking for peers. forceUseAllPeers: ${forceUseAllPeers}, maxAttempts: ${maxAttempts}` - ); - - for (let attempts = 0; attempts < maxAttempts; attempts++) { - this.log.info( - `Attempt ${attempts + 1}/${maxAttempts} to reach required number of peers` - ); - await this.maintainPeers(); - - if (!forceUseAllPeers && this.connectedPeers.length > 0) { - this.log.info( - `At least one peer connected (${this.connectedPeers.length}), not forcing use of all peers` - ); - return true; - } - - if (this.connectedPeers.length >= this.numPeersToUse) { - this.log.info( - `Required number of peers (${this.numPeersToUse}) reached` - ); - return true; - } - - this.log.warn( - `Found only ${this.connectedPeers.length}/${this.numPeersToUse} required peers. Retrying...` - ); - } - - this.log.error( - `Failed to find required number of peers (${this.numPeersToUse}) after ${maxAttempts} attempts` - ); - return false; - } - - /** - * Starts an interval to maintain the peers list to `numPeersToUse`. - * @param interval The interval in milliseconds to maintain the peers. - */ - private async startMaintainPeersInterval(interval: number): Promise { - this.log.info( - `Starting maintain peers interval with ${interval}ms interval` - ); - try { - this.maintainPeersIntervalId = setInterval(() => { - this.log.info("Running scheduled peer maintenance"); - this.maintainPeers().catch((error) => { - this.log.error("Error during scheduled peer maintenance:", error); - }); - }, interval); - this.log.info("Maintain peers interval started successfully"); - } catch (error) { - this.log.error("Error starting maintain peers interval:", error); - throw error; - } - } - - /** - * Maintains the peers list to `numPeersToUse`. - */ - private async maintainPeers(): Promise { - try { - const currentPeerCount = await this.peerManager.getPeerCount(); - const numPeersToAdd = this.numPeersToUse - currentPeerCount; - - this.log.info( - `Current peer count: ${currentPeerCount}, target: ${this.numPeersToUse}` - ); - - if (numPeersToAdd === 0) { - this.log.info("Peer count is at target, no maintenance required"); - return; - } - - if (numPeersToAdd > 0) { - this.log.info(`Attempting to add ${numPeersToAdd} peer(s)`); - await this.peerManager.findAndAddPeers(numPeersToAdd); - } else { - this.log.info( - `Attempting to remove ${Math.abs(numPeersToAdd)} excess peer(s)` - ); - await this.peerManager.removeExcessPeers(Math.abs(numPeersToAdd)); - } - - const finalPeerCount = await this.peerManager.getPeerCount(); - this.log.info( - `Peer maintenance completed. Initial count: ${currentPeerCount}, Final count: ${finalPeerCount}` - ); - } catch (error) { - this.log.error("Error during peer maintenance", { error }); - } - } -} diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index 1db0ed1d37..d0cfa50a44 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -9,7 +9,6 @@ import { type ILightPush, type Libp2p, NetworkConfig, - type ProtocolCreateOptions, ProtocolError, type ProtocolUseOptions, type PubsubTopic, @@ -25,7 +24,6 @@ import { toAsyncIterator } from "@waku/utils"; -import { BaseProtocolSDK } from "../base_protocol.js"; import { PeerManager } from "../peer_manager.js"; import { DEFAULT_SUBSCRIBE_OPTIONS } from "./constants.js"; @@ -33,7 +31,7 @@ import { SubscriptionManager } from "./subscription_manager.js"; const log = new Logger("sdk:filter"); -class Filter extends BaseProtocolSDK implements IFilter { +class Filter implements IFilter { public readonly protocol: FilterCore; private activeSubscriptions = new Map(); @@ -41,32 +39,25 @@ class Filter extends BaseProtocolSDK implements IFilter { public constructor( private connectionManager: ConnectionManager, private libp2p: Libp2p, - peerManager: PeerManager, - private lightPush?: ILightPush, - options?: ProtocolCreateOptions + private peerManager: PeerManager, + private lightPush?: ILightPush ) { - super( - new FilterCore( - async (pubsubTopic, wakuMessage, peerIdStr) => { - const subscription = this.getActiveSubscription(pubsubTopic); - if (!subscription) { - log.error( - `No subscription locally registered for topic ${pubsubTopic}` - ); - return; - } - await subscription.processIncomingMessage(wakuMessage, peerIdStr); - }, - - connectionManager.pubsubTopics, - libp2p - ), - peerManager, - { numPeersToUse: options?.numPeersToUse } + this.protocol = new FilterCore( + async (pubsubTopic, wakuMessage, peerIdStr) => { + const subscription = this.getActiveSubscription(pubsubTopic); + if (!subscription) { + log.error( + `No subscription locally registered for topic ${pubsubTopic}` + ); + return; + } + await subscription.processIncomingMessage(wakuMessage, peerIdStr); + }, + + connectionManager.pubsubTopics, + libp2p ); - this.protocol = this.core as FilterCore; - this.activeSubscriptions = new Map(); } @@ -177,7 +168,7 @@ class Filter extends BaseProtocolSDK implements IFilter { ensurePubsubTopicIsConfigured(pubsubTopic, this.protocol.pubsubTopics); - const hasPeers = await this.hasPeers(options); + const hasPeers = await this.peerManager.hasPeersWithMaintain(options); if (!hasPeers) { return { error: ProtocolError.NO_PEER_AVAILABLE, @@ -186,8 +177,8 @@ class Filter extends BaseProtocolSDK implements IFilter { } log.info( - `Creating filter subscription with ${this.connectedPeers.length} peers: `, - this.connectedPeers.map((peer) => peer.id.toString()) + `Creating filter subscription with ${this.peerManager.connectedPeers.length} peers: `, + this.peerManager.connectedPeers.map((peer) => peer.id.toString()) ); const subscription = @@ -198,8 +189,8 @@ class Filter extends BaseProtocolSDK implements IFilter { pubsubTopic, this.protocol, this.connectionManager, - () => this.connectedPeers, - this.renewPeer.bind(this), + () => this.peerManager.connectedPeers, + this.peerManager.renewPeer.bind(this), this.libp2p, this.lightPush ) @@ -307,9 +298,8 @@ class Filter extends BaseProtocolSDK implements IFilter { export function wakuFilter( connectionManager: ConnectionManager, peerManager: PeerManager, - lightPush?: ILightPush, - init?: ProtocolCreateOptions + lightPush?: ILightPush ): (libp2p: Libp2p) => IFilter { return (libp2p: Libp2p) => - new Filter(connectionManager, libp2p, peerManager, lightPush, init); + new Filter(connectionManager, libp2p, peerManager, lightPush); } diff --git a/packages/sdk/src/protocols/light_push/light_push.spec.ts b/packages/sdk/src/protocols/light_push/light_push.spec.ts index afc2da78e2..1e0a24293c 100644 --- a/packages/sdk/src/protocols/light_push/light_push.spec.ts +++ b/packages/sdk/src/protocols/light_push/light_push.spec.ts @@ -157,8 +157,7 @@ function mockLightPush(options: MockLightPushOptions): LightPush { { configuredPubsubTopics: options.pubsubTopics || [PUBSUB_TOPIC] } as ConnectionManager, - options.libp2p, - { numPeersToUse: options.numPeersToUse } + options.libp2p ); } diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/protocols/light_push/light_push.ts index 478e683b7d..fb1ca55d22 100644 --- a/packages/sdk/src/protocols/light_push/light_push.ts +++ b/packages/sdk/src/protocols/light_push/light_push.ts @@ -1,10 +1,5 @@ import type { Peer, PeerId } from "@libp2p/interface"; -import { - ConnectionManager, - getHealthManager, - LightPushCodec, - LightPushCore -} from "@waku/core"; +import { ConnectionManager, getHealthManager, LightPushCore } from "@waku/core"; import { type CoreProtocolResult, Failure, @@ -13,13 +8,12 @@ import { type IMessage, type ISenderOptions, type Libp2p, - type ProtocolCreateOptions, ProtocolError, SDKProtocolResult } from "@waku/interfaces"; import { ensurePubsubTopicIsConfigured, Logger } from "@waku/utils"; -import { DEFAULT_NUM_PEERS_TO_USE } from "../base_protocol.js"; +import { PeerManager } from "../peer_manager.js"; const log = new Logger("sdk:light-push"); @@ -32,15 +26,13 @@ const DEFAULT_SEND_OPTIONS: ISenderOptions = { type RetryCallback = (peer: Peer) => Promise; export class LightPush implements ILightPush { - private numPeersToUse: number = DEFAULT_NUM_PEERS_TO_USE; public readonly protocol: LightPushCore; public constructor( - private connectionManager: ConnectionManager, - libp2p: Libp2p, - options?: ProtocolCreateOptions + connectionManager: ConnectionManager, + private peerManager: PeerManager, + libp2p: Libp2p ) { - this.numPeersToUse = options?.numPeersToUse ?? DEFAULT_NUM_PEERS_TO_USE; this.protocol = new LightPushCore(connectionManager.pubsubTopics, libp2p); } @@ -67,7 +59,7 @@ export class LightPush implements ILightPush { }; } - const peers = await this.getConnectedPeers(); + const peers = this.peerManager.getPeers(); if (peers.length === 0) { return { successes, @@ -125,7 +117,7 @@ export class LightPush implements ILightPush { maxAttempts?: number ): Promise { maxAttempts = maxAttempts || DEFAULT_MAX_ATTEMPTS; - const connectedPeers = await this.getConnectedPeers(); + const connectedPeers = this.peerManager.getPeers(); if (connectedPeers.length === 0) { log.warn("Cannot retry with no connected peers."); @@ -145,17 +137,12 @@ export class LightPush implements ILightPush { ); } } - - private async getConnectedPeers(): Promise { - const peers = - await this.connectionManager.getConnectedPeers(LightPushCodec); - return peers.slice(0, this.numPeersToUse); - } } export function wakuLightPush( connectionManager: ConnectionManager, - init: Partial = {} + peerManager: PeerManager ): (libp2p: Libp2p) => ILightPush { - return (libp2p: Libp2p) => new LightPush(connectionManager, libp2p, init); + return (libp2p: Libp2p) => + new LightPush(connectionManager, peerManager, libp2p); } diff --git a/packages/sdk/src/protocols/peer_manager.ts b/packages/sdk/src/protocols/peer_manager.ts index d8bf25f0bc..4d9b98a4d5 100644 --- a/packages/sdk/src/protocols/peer_manager.ts +++ b/packages/sdk/src/protocols/peer_manager.ts @@ -1,10 +1,24 @@ import { Peer, PeerId } from "@libp2p/interface"; import { ConnectionManager } from "@waku/core"; +import { ProtocolUseOptions } from "@waku/interfaces"; import { Logger } from "@waku/utils"; import { Mutex } from "async-mutex"; const log = new Logger("peer-manager"); +const DEFAULT_NUM_PEERS_TO_USE = 2; +const DEFAULT_MAINTAIN_PEERS_INTERVAL = 30_000; + +type PeerManagerConfig = { + numPeersToUse?: number; + maintainPeersIntervalMs?: number; +}; + +type PeerManagerParams = { + config?: PeerManagerConfig; + connectionManager: ConnectionManager; +}; + export class PeerManager { private peers: Map = new Map(); @@ -12,16 +26,176 @@ export class PeerManager { private writeMutex = new Mutex(); private writeLockHolder: string | null = null; - public constructor(private readonly connectionManager: ConnectionManager) {} + private readonly numPeersToUse: number; + private readonly maintainPeersIntervalMs: number; + private readonly connectionManager: ConnectionManager; + + private maintainPeersIntervalId: ReturnType< + typeof window.setInterval + > | null = null; + + public constructor(params: PeerManagerParams) { + this.numPeersToUse = + params?.config?.numPeersToUse || DEFAULT_NUM_PEERS_TO_USE; + this.maintainPeersIntervalMs = + params?.config?.maintainPeersIntervalMs || + DEFAULT_MAINTAIN_PEERS_INTERVAL; + + this.connectionManager = params.connectionManager; + + void this.startMaintainPeersInterval(this.maintainPeersIntervalMs); + } + + /** + * Disconnects from a peer and tries to find a new one to replace it. + * @param peerToDisconnect The peer to disconnect from. + * @returns The new peer that was found and connected to. + */ + public async renewPeer(peerToDisconnect: PeerId): Promise { + log.info(`Attempting to renew peer ${peerToDisconnect}`); + + const newPeer = await this.findPeers(1); + if (newPeer.length === 0) { + log.error("Failed to find a new peer to replace the disconnected one"); + return undefined; + } + + await this.removePeer(peerToDisconnect); + await this.addPeer(newPeer[0]); + + log.info(`Successfully renewed peer. New peer: ${newPeer[0].id}`); + + return newPeer[0]; + } + + /** + * Stops the maintain peers interval. + */ + public stopMaintainPeersInterval(): void { + if (this.maintainPeersIntervalId) { + clearInterval(this.maintainPeersIntervalId); + this.maintainPeersIntervalId = null; + log.info("Maintain peers interval stopped"); + } else { + log.info("Maintain peers interval was not running"); + } + } + + /** + * Checks if there are sufficient peers to send a message to. + * If `forceUseAllPeers` is `false` (default), returns `true` if there are any connected peers. + * If `forceUseAllPeers` is `true`, attempts to connect to `numPeersToUse` peers. + * @param options Optional options object + * @param options.forceUseAllPeers Optional flag to force connecting to `numPeersToUse` peers (default: false) + * @param options.maxAttempts Optional maximum number of attempts to reach the required number of peers (default: 3) + * @returns `true` if the required number of peers are connected, `false` otherwise + */ + public async hasPeersWithMaintain( + options: Partial = {} + ): Promise { + const { forceUseAllPeers = false, maxAttempts = 3 } = options; + + log.info( + `Checking for peers. forceUseAllPeers: ${forceUseAllPeers}, maxAttempts: ${maxAttempts}` + ); + + for (let attempts = 0; attempts < maxAttempts; attempts++) { + log.info( + `Attempt ${attempts + 1}/${maxAttempts} to reach required number of peers` + ); + await this.maintainPeers(); + + if (!forceUseAllPeers && this.connectedPeers.length > 0) { + log.info( + `At least one peer connected (${this.connectedPeers.length}), not forcing use of all peers` + ); + return true; + } + + if (this.connectedPeers.length >= this.numPeersToUse) { + log.info(`Required number of peers (${this.numPeersToUse}) reached`); + return true; + } + + log.warn( + `Found only ${this.connectedPeers.length}/${this.numPeersToUse} required peers. Retrying...` + ); + } + + log.error( + `Failed to find required number of peers (${this.numPeersToUse}) after ${maxAttempts} attempts` + ); + return false; + } + + /** + * Starts an interval to maintain the peers list to `numPeersToUse`. + * @param interval The interval in milliseconds to maintain the peers. + */ + private async startMaintainPeersInterval(interval: number): Promise { + log.info(`Starting maintain peers interval with ${interval}ms interval`); + try { + this.maintainPeersIntervalId = setInterval(() => { + log.info("Running scheduled peer maintenance"); + this.maintainPeers().catch((error) => { + log.error("Error during scheduled peer maintenance:", error); + }); + }, interval); + log.info("Maintain peers interval started successfully"); + } catch (error) { + log.error("Error starting maintain peers interval:", error); + throw error; + } + } + + /** + * Maintains the peers list to `numPeersToUse`. + */ + private async maintainPeers(): Promise { + try { + const currentPeerCount = await this.getPeerCount(); + const numPeersToAdd = this.numPeersToUse - currentPeerCount; + + log.info( + `Current peer count: ${currentPeerCount}, target: ${this.numPeersToUse}` + ); + + if (numPeersToAdd === 0) { + log.info("Peer count is at target, no maintenance required"); + return; + } + + if (numPeersToAdd > 0) { + log.info(`Attempting to add ${numPeersToAdd} peer(s)`); + await this.findAndAddPeers(numPeersToAdd); + } else { + log.info( + `Attempting to remove ${Math.abs(numPeersToAdd)} excess peer(s)` + ); + await this.removeExcessPeers(Math.abs(numPeersToAdd)); + } + + const finalPeerCount = await this.getPeerCount(); + log.info( + `Peer maintenance completed. Initial count: ${currentPeerCount}, Final count: ${finalPeerCount}` + ); + } catch (error) { + log.error("Error during peer maintenance", { error }); + } + } public getWriteLockHolder(): string | null { return this.writeLockHolder; } - public getPeers(): Peer[] { + public get connectedPeers(): Peer[] { return Array.from(this.peers.values()); } + public getPeers(): Peer[] { + return this.connectedPeers.slice(0, this.numPeersToUse); + } + public async addPeer(peer: Peer): Promise { return this.writeMutex.runExclusive(async () => { this.writeLockHolder = `addPeer: ${peer.id.toString()}`; diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index 36249a0f2f..cd8029618b 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -10,29 +10,23 @@ import { import { messageHash } from "@waku/message-hash"; import { ensurePubsubTopicIsConfigured, isDefined, Logger } from "@waku/utils"; -import { BaseProtocolSDK } from "../base_protocol.js"; import { PeerManager } from "../peer_manager.js"; -const DEFAULT_NUM_PEERS = 1; - const log = new Logger("waku:store:sdk"); /** * StoreSDK is an implementation of the IStoreSDK interface. * It provides methods to interact with the Waku Store protocol. */ -export class Store extends BaseProtocolSDK implements IStore { +export class Store implements IStore { public readonly protocol: StoreCore; public constructor( - private connectionManager: ConnectionManager, + connectionManager: ConnectionManager, libp2p: Libp2p, - peerManager: PeerManager + private peerManager: PeerManager ) { - super(new StoreCore(connectionManager.pubsubTopics, libp2p), peerManager, { - numPeersToUse: DEFAULT_NUM_PEERS - }); - this.protocol = this.core as StoreCore; + this.protocol = new StoreCore(connectionManager.pubsubTopics, libp2p); } /** @@ -59,9 +53,7 @@ export class Store extends BaseProtocolSDK implements IStore { ...options }; - const peers = await this.connectionManager.getConnectedPeers( - this.core.multicodec - ); + const peers = this.peerManager.getPeers(); const peer = peers[0]; if (!peer) { diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index 90572896d9..c4cdabae3a 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -83,7 +83,12 @@ export class WakuNode implements IWaku { config: options?.connectionManager }); - this.peerManager = new PeerManager(this.connectionManager); + this.peerManager = new PeerManager({ + config: { + numPeersToUse: options.numPeersToUse + }, + connectionManager: this.connectionManager + }); this.health = getHealthManager(); @@ -93,7 +98,7 @@ export class WakuNode implements IWaku { } if (protocolsEnabled.lightpush) { - const lightPush = wakuLightPush(this.connectionManager, options); + const lightPush = wakuLightPush(this.connectionManager, this.peerManager); this.lightPush = lightPush(libp2p); } @@ -101,8 +106,7 @@ export class WakuNode implements IWaku { const filter = wakuFilter( this.connectionManager, this.peerManager, - this.lightPush, - options + this.lightPush ); this.filter = filter(libp2p); } From d0c6905005f881ee592b351929e6c03296399477 Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 24 Oct 2024 01:18:25 +0200 Subject: [PATCH 09/22] re-implement peerManager, remove ProtocolUseOptions --- packages/core/src/lib/light_push/index.ts | 1 + packages/interfaces/src/filter.ts | 2 - packages/interfaces/src/protocols.ts | 15 - packages/sdk/package.json | 1 - packages/sdk/src/protocols/filter/index.ts | 27 +- .../protocols/filter/subscription_manager.ts | 27 +- .../src/protocols/light_push/light_push.ts | 4 +- packages/sdk/src/protocols/peer_manager.ts | 278 ++++-------------- packages/sdk/src/protocols/store/index.ts | 2 +- packages/sdk/src/reliability_monitor/index.ts | 13 +- .../sdk/src/reliability_monitor/receiver.ts | 18 +- packages/sdk/src/waku/waku.ts | 5 +- 12 files changed, 104 insertions(+), 289 deletions(-) diff --git a/packages/core/src/lib/light_push/index.ts b/packages/core/src/lib/light_push/index.ts index 440201464f..d649df7a21 100644 --- a/packages/core/src/lib/light_push/index.ts +++ b/packages/core/src/lib/light_push/index.ts @@ -76,6 +76,7 @@ export class LightPushCore extends BaseProtocol implements IBaseProtocolCore { } } + // TODO(weboko): use peer.id as parameter instead public async send( encoder: IEncoder, message: IMessage, diff --git a/packages/interfaces/src/filter.ts b/packages/interfaces/src/filter.ts index 373aac5121..f992a497a7 100644 --- a/packages/interfaces/src/filter.ts +++ b/packages/interfaces/src/filter.ts @@ -6,7 +6,6 @@ import type { Callback, IBaseProtocolCore, ProtocolError, - ProtocolUseOptions, SDKProtocolResult } from "./protocols.js"; import type { IReceiver } from "./receiver.js"; @@ -40,7 +39,6 @@ export type IFilter = IReceiver & { protocol: IBaseProtocolCore } & { subscribe( decoders: IDecoder | IDecoder[], callback: Callback, - protocolUseOptions?: ProtocolUseOptions, subscribeOptions?: SubscribeOptions ): Promise; }; diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts index 42e4708911..00d330152e 100644 --- a/packages/interfaces/src/protocols.ts +++ b/packages/interfaces/src/protocols.ts @@ -22,21 +22,6 @@ export type IBaseProtocolCore = { export type NetworkConfig = StaticSharding | AutoSharding; -//TODO: merge this with ProtocolCreateOptions or establish distinction: https://github.com/waku-org/js-waku/issues/2048 -/** - * Options for using LightPush and Filter - */ -export type ProtocolUseOptions = { - /** - * Optional flag to force using all available peers - */ - forceUseAllPeers?: boolean; - /** - * Optional maximum number of attempts for exponential backoff - */ - maxAttempts?: number; -}; - export type ProtocolCreateOptions = { /** * Configuration for determining the network in use. diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 086024363e..aabe7ab68e 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -73,7 +73,6 @@ "@waku/proto": "^0.0.8", "@waku/utils": "0.0.21", "@waku/message-hash": "0.1.17", - "async-mutex": "^0.5.0", "libp2p": "^1.8.1" }, "devDependencies": { diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index d0cfa50a44..a70e484793 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -10,7 +10,6 @@ import { type Libp2p, NetworkConfig, ProtocolError, - type ProtocolUseOptions, type PubsubTopic, type SubscribeOptions, SubscribeResult, @@ -67,7 +66,6 @@ class Filter implements IFilter { * * @param {IDecoder | IDecoder[]} decoders - A single decoder or an array of decoders to use for decoding messages. * @param {Callback} callback - The callback function to be invoked with decoded messages. - * @param {ProtocolUseOptions} [protocolUseOptions] - Optional settings for using the protocol. * @param {SubscribeOptions} [subscribeOptions=DEFAULT_SUBSCRIBE_OPTIONS] - Options for the subscription. * * @returns {Promise} A promise that resolves to an object containing: @@ -103,7 +101,6 @@ class Filter implements IFilter { public async subscribe( decoders: IDecoder | IDecoder[], callback: Callback, - protocolUseOptions?: ProtocolUseOptions, subscribeOptions: SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS ): Promise { const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); @@ -118,10 +115,7 @@ class Filter implements IFilter { const pubsubTopic = uniquePubsubTopics[0]; - const { subscription, error } = await this.createSubscription( - pubsubTopic, - protocolUseOptions - ); + const { subscription, error } = await this.createSubscription(pubsubTopic); if (error) { return { @@ -153,14 +147,8 @@ class Filter implements IFilter { * @returns The subscription object. */ private async createSubscription( - pubsubTopicShardInfo: NetworkConfig | PubsubTopic, - options?: ProtocolUseOptions + pubsubTopicShardInfo: NetworkConfig | PubsubTopic ): Promise { - options = { - autoRetry: true, - ...options - } as ProtocolUseOptions; - const pubsubTopic = typeof pubsubTopicShardInfo == "string" ? pubsubTopicShardInfo @@ -168,8 +156,8 @@ class Filter implements IFilter { ensurePubsubTopicIsConfigured(pubsubTopic, this.protocol.pubsubTopics); - const hasPeers = await this.peerManager.hasPeersWithMaintain(options); - if (!hasPeers) { + const peers = await this.peerManager.getPeers(); + if (peers.length === 0) { return { error: ProtocolError.NO_PEER_AVAILABLE, subscription: null @@ -177,8 +165,8 @@ class Filter implements IFilter { } log.info( - `Creating filter subscription with ${this.peerManager.connectedPeers.length} peers: `, - this.peerManager.connectedPeers.map((peer) => peer.id.toString()) + `Creating filter subscription with ${peers.length} peers: `, + peers.map((peer) => peer.id.toString()) ); const subscription = @@ -189,8 +177,7 @@ class Filter implements IFilter { pubsubTopic, this.protocol, this.connectionManager, - () => this.peerManager.connectedPeers, - this.peerManager.renewPeer.bind(this), + this.peerManager, this.libp2p, this.lightPush ) diff --git a/packages/sdk/src/protocols/filter/subscription_manager.ts b/packages/sdk/src/protocols/filter/subscription_manager.ts index 1dc2724ab5..59af175121 100644 --- a/packages/sdk/src/protocols/filter/subscription_manager.ts +++ b/packages/sdk/src/protocols/filter/subscription_manager.ts @@ -30,6 +30,7 @@ import { groupByContentTopic, Logger } from "@waku/utils"; import { ReliabilityMonitorManager } from "../../reliability_monitor/index.js"; import { ReceiverReliabilityMonitor } from "../../reliability_monitor/receiver.js"; +import { PeerManager } from "../peer_manager.js"; import { DEFAULT_KEEP_ALIVE, @@ -57,10 +58,7 @@ export class SubscriptionManager implements ISubscription { private readonly pubsubTopic: PubsubTopic, private readonly protocol: FilterCore, private readonly connectionManager: ConnectionManager, - private readonly getPeers: () => Peer[], - private readonly renewPeer: ( - peerToDisconnect: PeerId - ) => Promise, + private readonly peerManager: PeerManager, private readonly libp2p: Libp2p, private readonly lightPush?: ILightPush ) { @@ -69,11 +67,9 @@ export class SubscriptionManager implements ISubscription { this.reliabilityMonitor = ReliabilityMonitorManager.createReceiverMonitor( this.pubsubTopic, - this.getPeers.bind(this), - this.renewPeer.bind(this), + this.peerManager, () => Array.from(this.subscriptionCallbacks.keys()), this.protocol.subscribe.bind(this.protocol), - this.protocol.addLibp2pEventListener.bind(this.protocol), this.sendLightPushCheckMessage.bind(this) ); } @@ -116,7 +112,8 @@ export class SubscriptionManager implements ISubscription { const decodersGroupedByCT = groupByContentTopic(decodersArray); const contentTopics = Array.from(decodersGroupedByCT.keys()); - const promises = this.getPeers().map(async (peer) => + const peers = await this.peerManager.getPeers(); + const promises = peers.map(async (peer) => this.subscribeWithPeerVerification(peer, contentTopics) ); @@ -153,7 +150,8 @@ export class SubscriptionManager implements ISubscription { public async unsubscribe( contentTopics: ContentTopic[] ): Promise { - const promises = this.getPeers().map(async (peer) => { + const peers = await this.peerManager.getPeers(); + const promises = peers.map(async (peer) => { const response = await this.protocol.unsubscribe( this.pubsubTopic, peer, @@ -179,7 +177,9 @@ export class SubscriptionManager implements ISubscription { public async ping(peerId?: PeerId): Promise { log.info("Sending keep-alive ping"); - const peers = peerId ? [peerId] : this.getPeers().map((peer) => peer.id); + const peers = peerId + ? [peerId] + : (await this.peerManager.getPeers()).map((peer) => peer.id); const promises = peers.map((peerId) => this.pingSpecificPeer(peerId)); const results = await Promise.allSettled(promises); @@ -188,7 +188,8 @@ export class SubscriptionManager implements ISubscription { } public async unsubscribeAll(): Promise { - const promises = this.getPeers().map(async (peer) => + const peers = await this.peerManager.getPeers(); + const promises = peers.map(async (peer) => this.protocol.unsubscribeAll(this.pubsubTopic, peer) ); @@ -241,6 +242,7 @@ export class SubscriptionManager implements ISubscription { peer, contentTopics ); + await this.sendLightPushCheckMessage(peer); return result; } @@ -271,7 +273,8 @@ export class SubscriptionManager implements ISubscription { } private async pingSpecificPeer(peerId: PeerId): Promise { - const peer = this.getPeers().find((p) => p.id.equals(peerId)); + const peers = await this.peerManager.getPeers(); + const peer = peers.find((p) => p.id.equals(peerId)); if (!peer) { return { success: null, diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/protocols/light_push/light_push.ts index fb1ca55d22..f04f3175b6 100644 --- a/packages/sdk/src/protocols/light_push/light_push.ts +++ b/packages/sdk/src/protocols/light_push/light_push.ts @@ -59,7 +59,7 @@ export class LightPush implements ILightPush { }; } - const peers = this.peerManager.getPeers(); + const peers = await this.peerManager.getPeers(); if (peers.length === 0) { return { successes, @@ -117,7 +117,7 @@ export class LightPush implements ILightPush { maxAttempts?: number ): Promise { maxAttempts = maxAttempts || DEFAULT_MAX_ATTEMPTS; - const connectedPeers = this.peerManager.getPeers(); + const connectedPeers = await this.peerManager.getPeers(); if (connectedPeers.length === 0) { log.warn("Cannot retry with no connected peers."); diff --git a/packages/sdk/src/protocols/peer_manager.ts b/packages/sdk/src/protocols/peer_manager.ts index 4d9b98a4d5..c4d26d9ae4 100644 --- a/packages/sdk/src/protocols/peer_manager.ts +++ b/packages/sdk/src/protocols/peer_manager.ts @@ -1,271 +1,123 @@ -import { Peer, PeerId } from "@libp2p/interface"; -import { ConnectionManager } from "@waku/core"; -import { ProtocolUseOptions } from "@waku/interfaces"; +import { Connection, Peer, PeerId } from "@libp2p/interface"; +import { Libp2p } from "@waku/interfaces"; import { Logger } from "@waku/utils"; -import { Mutex } from "async-mutex"; const log = new Logger("peer-manager"); const DEFAULT_NUM_PEERS_TO_USE = 2; -const DEFAULT_MAINTAIN_PEERS_INTERVAL = 30_000; +const CONNECTION_LOCK_TAG = "peer-manager-lock"; type PeerManagerConfig = { numPeersToUse?: number; - maintainPeersIntervalMs?: number; }; type PeerManagerParams = { + libp2p: Libp2p; config?: PeerManagerConfig; - connectionManager: ConnectionManager; }; export class PeerManager { - private peers: Map = new Map(); - - private readMutex = new Mutex(); - private writeMutex = new Mutex(); - private writeLockHolder: string | null = null; - private readonly numPeersToUse: number; - private readonly maintainPeersIntervalMs: number; - private readonly connectionManager: ConnectionManager; - private maintainPeersIntervalId: ReturnType< - typeof window.setInterval - > | null = null; + private readonly libp2p: Libp2p; public constructor(params: PeerManagerParams) { this.numPeersToUse = params?.config?.numPeersToUse || DEFAULT_NUM_PEERS_TO_USE; - this.maintainPeersIntervalMs = - params?.config?.maintainPeersIntervalMs || - DEFAULT_MAINTAIN_PEERS_INTERVAL; - this.connectionManager = params.connectionManager; + this.libp2p = params.libp2p; - void this.startMaintainPeersInterval(this.maintainPeersIntervalMs); + this.startConnectionListener(); } - /** - * Disconnects from a peer and tries to find a new one to replace it. - * @param peerToDisconnect The peer to disconnect from. - * @returns The new peer that was found and connected to. - */ - public async renewPeer(peerToDisconnect: PeerId): Promise { - log.info(`Attempting to renew peer ${peerToDisconnect}`); - - const newPeer = await this.findPeers(1); - if (newPeer.length === 0) { - log.error("Failed to find a new peer to replace the disconnected one"); - return undefined; - } - - await this.removePeer(peerToDisconnect); - await this.addPeer(newPeer[0]); - - log.info(`Successfully renewed peer. New peer: ${newPeer[0].id}`); - - return newPeer[0]; + public stop(): void { + this.stopConnectionListener(); } - /** - * Stops the maintain peers interval. - */ - public stopMaintainPeersInterval(): void { - if (this.maintainPeersIntervalId) { - clearInterval(this.maintainPeersIntervalId); - this.maintainPeersIntervalId = null; - log.info("Maintain peers interval stopped"); - } else { - log.info("Maintain peers interval was not running"); - } - } - - /** - * Checks if there are sufficient peers to send a message to. - * If `forceUseAllPeers` is `false` (default), returns `true` if there are any connected peers. - * If `forceUseAllPeers` is `true`, attempts to connect to `numPeersToUse` peers. - * @param options Optional options object - * @param options.forceUseAllPeers Optional flag to force connecting to `numPeersToUse` peers (default: false) - * @param options.maxAttempts Optional maximum number of attempts to reach the required number of peers (default: 3) - * @returns `true` if the required number of peers are connected, `false` otherwise - */ - public async hasPeersWithMaintain( - options: Partial = {} - ): Promise { - const { forceUseAllPeers = false, maxAttempts = 3 } = options; - - log.info( - `Checking for peers. forceUseAllPeers: ${forceUseAllPeers}, maxAttempts: ${maxAttempts}` + public async getPeers(): Promise { + return Promise.all( + this.getLockedConnections().map((c) => this.mapConnectionToPeer(c)) ); + } - for (let attempts = 0; attempts < maxAttempts; attempts++) { - log.info( - `Attempt ${attempts + 1}/${maxAttempts} to reach required number of peers` - ); - await this.maintainPeers(); - - if (!forceUseAllPeers && this.connectedPeers.length > 0) { - log.info( - `At least one peer connected (${this.connectedPeers.length}), not forcing use of all peers` - ); - return true; - } - - if (this.connectedPeers.length >= this.numPeersToUse) { - log.info(`Required number of peers (${this.numPeersToUse}) reached`); - return true; - } + public async requestRenew( + peerId: PeerId | string + ): Promise { + const lockedConnections = this.getLockedConnections(); + const neededPeers = this.numPeersToUse - lockedConnections.length; - log.warn( - `Found only ${this.connectedPeers.length}/${this.numPeersToUse} required peers. Retrying...` - ); + if (neededPeers === 0) { + return; } - log.error( - `Failed to find required number of peers (${this.numPeersToUse}) after ${maxAttempts} attempts` + const result = await Promise.all( + this.getUnlockedConnections() + .filter((c) => !c.remotePeer.equals(peerId)) + .slice(0, neededPeers) + .map((c) => this.lockConnection(c)) + .map((c) => this.mapConnectionToPeer(c)) ); - return false; - } - /** - * Starts an interval to maintain the peers list to `numPeersToUse`. - * @param interval The interval in milliseconds to maintain the peers. - */ - private async startMaintainPeersInterval(interval: number): Promise { - log.info(`Starting maintain peers interval with ${interval}ms interval`); - try { - this.maintainPeersIntervalId = setInterval(() => { - log.info("Running scheduled peer maintenance"); - this.maintainPeers().catch((error) => { - log.error("Error during scheduled peer maintenance:", error); - }); - }, interval); - log.info("Maintain peers interval started successfully"); - } catch (error) { - log.error("Error starting maintain peers interval:", error); - throw error; - } + return result[0]; } - /** - * Maintains the peers list to `numPeersToUse`. - */ - private async maintainPeers(): Promise { - try { - const currentPeerCount = await this.getPeerCount(); - const numPeersToAdd = this.numPeersToUse - currentPeerCount; - - log.info( - `Current peer count: ${currentPeerCount}, target: ${this.numPeersToUse}` - ); - - if (numPeersToAdd === 0) { - log.info("Peer count is at target, no maintenance required"); - return; - } - - if (numPeersToAdd > 0) { - log.info(`Attempting to add ${numPeersToAdd} peer(s)`); - await this.findAndAddPeers(numPeersToAdd); - } else { - log.info( - `Attempting to remove ${Math.abs(numPeersToAdd)} excess peer(s)` - ); - await this.removeExcessPeers(Math.abs(numPeersToAdd)); - } - - const finalPeerCount = await this.getPeerCount(); - log.info( - `Peer maintenance completed. Initial count: ${currentPeerCount}, Final count: ${finalPeerCount}` - ); - } catch (error) { - log.error("Error during peer maintenance", { error }); - } + private startConnectionListener(): void { + this.libp2p.addEventListener("peer:connect", this.onConnected); + this.libp2p.addEventListener("peer:disconnect", this.onDisconnected); } - public getWriteLockHolder(): string | null { - return this.writeLockHolder; + private stopConnectionListener(): void { + this.libp2p.removeEventListener("peer:connect", this.onConnected); + this.libp2p.removeEventListener("peer:disconnect", this.onDisconnected); } - public get connectedPeers(): Peer[] { - return Array.from(this.peers.values()); + private onConnected(event: CustomEvent): void { + const peerId = event.detail; + void this.lockPeerIfNeeded(peerId); } - public getPeers(): Peer[] { - return this.connectedPeers.slice(0, this.numPeersToUse); + private onDisconnected(event: CustomEvent): void { + const peerId = event.detail; + void this.requestRenew(peerId); } - public async addPeer(peer: Peer): Promise { - return this.writeMutex.runExclusive(async () => { - this.writeLockHolder = `addPeer: ${peer.id.toString()}`; - await this.connectionManager.attemptDial(peer.id); - this.peers.set(peer.id.toString(), peer); - log.info(`Added and dialed peer: ${peer.id.toString()}`); - this.writeLockHolder = null; - }); - } + private async lockPeerIfNeeded(peerId: PeerId): Promise { + const lockedConnections = this.getLockedConnections(); + const neededPeers = this.numPeersToUse - lockedConnections.length; - public async removePeer(peerId: PeerId): Promise { - return this.writeMutex.runExclusive(() => { - this.writeLockHolder = `removePeer: ${peerId.toString()}`; - this.peers.delete(peerId.toString()); - log.info(`Removed peer: ${peerId.toString()}`); - this.writeLockHolder = null; - }); - } + if (neededPeers === 0) { + return; + } - public async getPeerCount(): Promise { - return this.readMutex.runExclusive(() => this.peers.size); + this.getUnlockedConnections() + .filter((c) => c.remotePeer.equals(peerId)) + .map((c) => this.lockConnection(c)); } - public async hasPeers(): Promise { - return this.readMutex.runExclusive(() => this.peers.size > 0); + private getLockedConnections(): Connection[] { + return this.libp2p + .getConnections() + .filter((c) => c.status === "open" && this.isConnectionLocked(c)); } - public async removeExcessPeers(excessPeers: number): Promise { - log.info(`Removing ${excessPeers} excess peer(s)`); - const peersToRemove = Array.from(this.peers.values()).slice(0, excessPeers); - for (const peer of peersToRemove) { - await this.removePeer(peer.id); - } + private getUnlockedConnections(): Connection[] { + return this.libp2p + .getConnections() + .filter((c) => c.status === "open" && !this.isConnectionLocked(c)); } - /** - * Finds and adds new peers to the peers list. - * @param numPeers The number of peers to find and add. - */ - public async findAndAddPeers(numPeers: number): Promise { - const additionalPeers = await this.findPeers(numPeers); - if (additionalPeers.length === 0) { - log.warn("No additional peers found"); - return []; - } - return this.addMultiplePeers(additionalPeers); + private lockConnection(c: Connection): Connection { + log.info(`Locking connection for peerId=${c.remotePeer.toString()}`); + c.tags.push(CONNECTION_LOCK_TAG); + return c; } - /** - * Finds additional peers. - * @param numPeers The number of peers to find. - */ - public async findPeers(numPeers: number): Promise { - const connectedPeers = await this.connectionManager.getConnectedPeers(); - - return this.readMutex.runExclusive(async () => { - const newPeers = connectedPeers - .filter((peer) => !this.peers.has(peer.id.toString())) - .slice(0, numPeers); - - return newPeers; - }); + private isConnectionLocked(c: Connection): boolean { + return c.tags.includes(CONNECTION_LOCK_TAG); } - public async addMultiplePeers(peers: Peer[]): Promise { - const addedPeers: Peer[] = []; - for (const peer of peers) { - await this.addPeer(peer); - addedPeers.push(peer); - } - return addedPeers; + private async mapConnectionToPeer(c: Connection): Promise { + const peerId = c.remotePeer; + return this.libp2p.peerStore.get(peerId); } } diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index cd8029618b..01cfd277eb 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -53,7 +53,7 @@ export class Store implements IStore { ...options }; - const peers = this.peerManager.getPeers(); + const peers = await this.peerManager.getPeers(); const peer = peers[0]; if (!peer) { diff --git a/packages/sdk/src/reliability_monitor/index.ts b/packages/sdk/src/reliability_monitor/index.ts index f92d1fa866..120d208404 100644 --- a/packages/sdk/src/reliability_monitor/index.ts +++ b/packages/sdk/src/reliability_monitor/index.ts @@ -1,11 +1,12 @@ -import type { Peer, PeerId } from "@libp2p/interface"; +import type { Peer } from "@libp2p/interface"; import { ContentTopic, CoreProtocolResult, - Libp2p, PubsubTopic } from "@waku/interfaces"; +import { PeerManager } from "../protocols/peer_manager.js"; + import { ReceiverReliabilityMonitor } from "./receiver.js"; export class ReliabilityMonitorManager { @@ -16,15 +17,13 @@ export class ReliabilityMonitorManager { public static createReceiverMonitor( pubsubTopic: PubsubTopic, - getPeers: () => Peer[], - renewPeer: (peerId: PeerId) => Promise, + peerManager: PeerManager, getContentTopics: () => ContentTopic[], protocolSubscribe: ( pubsubTopic: PubsubTopic, peer: Peer, contentTopics: ContentTopic[] ) => Promise, - addLibp2pEventListener: Libp2p["addEventListener"], sendLightPushMessage: (peer: Peer) => Promise ): ReceiverReliabilityMonitor { if (ReliabilityMonitorManager.receiverMonitors.has(pubsubTopic)) { @@ -33,11 +32,9 @@ export class ReliabilityMonitorManager { const monitor = new ReceiverReliabilityMonitor( pubsubTopic, - getPeers, - renewPeer, + peerManager, getContentTopics, protocolSubscribe, - addLibp2pEventListener, sendLightPushMessage ); ReliabilityMonitorManager.receiverMonitors.set(pubsubTopic, monitor); diff --git a/packages/sdk/src/reliability_monitor/receiver.ts b/packages/sdk/src/reliability_monitor/receiver.ts index ea58c1b17c..6e2c6d59dc 100644 --- a/packages/sdk/src/reliability_monitor/receiver.ts +++ b/packages/sdk/src/reliability_monitor/receiver.ts @@ -3,7 +3,6 @@ import { ContentTopic, CoreProtocolResult, IProtoMessage, - Libp2p, PeerIdStr, PubsubTopic } from "@waku/interfaces"; @@ -11,6 +10,8 @@ import { messageHashStr } from "@waku/message-hash"; import { Logger } from "@waku/utils"; import { bytesToUtf8 } from "@waku/utils/bytes"; +import { PeerManager } from "../protocols/peer_manager.js"; + const log = new Logger("sdk:receiver:reliability_monitor"); const DEFAULT_MAX_PINGS = 3; @@ -28,24 +29,15 @@ export class ReceiverReliabilityMonitor { public constructor( private readonly pubsubTopic: PubsubTopic, - private getPeers: () => Peer[], - private renewPeer: (peerId: PeerId) => Promise, + private readonly peerManager: PeerManager, private getContentTopics: () => ContentTopic[], private protocolSubscribe: ( pubsubTopic: PubsubTopic, peer: Peer, contentTopics: ContentTopic[] ) => Promise, - private addLibp2pEventListener: Libp2p["addEventListener"], private sendLightPushMessage: (peer: Peer) => Promise - ) { - this.addLibp2pEventListener("peer:disconnect", (evt) => { - const peerId = evt.detail; - if (this.getPeers().some((p) => p.id.equals(peerId))) { - void this.renewAndSubscribePeer(peerId); - } - }); - } + ) {} public setMaxPingFailures(value: number | undefined): void { if (value === undefined) { @@ -166,7 +158,7 @@ export class ReceiverReliabilityMonitor { this.peerRenewalLocks.add(peerIdStr); - const newPeer = await this.renewPeer(peerId); + const newPeer = await this.peerManager.requestRenew(peerId); if (!newPeer) { log.warn(`Failed to renew peer ${peerIdStr}: No new peer found.`); return; diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index c4cdabae3a..96706e1a13 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -84,10 +84,10 @@ export class WakuNode implements IWaku { }); this.peerManager = new PeerManager({ + libp2p, config: { numPeersToUse: options.numPeersToUse - }, - connectionManager: this.connectionManager + } }); this.health = getHealthManager(); @@ -192,6 +192,7 @@ export class WakuNode implements IWaku { public async stop(): Promise { ReliabilityMonitorManager.stopAll(); + this.peerManager.stop(); this.connectionManager.stop(); await this.libp2p.stop(); } From 984326bb53ef278e9eae1e3b9b1784e59a74470b Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 24 Oct 2024 01:58:44 +0200 Subject: [PATCH 10/22] remove not needed test, up lock --- package-lock.json | 1690 +++++++---------- .../tests/filter/peer_management.spec.ts | 323 ---- 2 files changed, 714 insertions(+), 1299 deletions(-) delete mode 100644 packages/tests/tests/filter/peer_management.spec.ts diff --git a/package-lock.json b/package-lock.json index 4501ba8551..518f07b4c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,12 +81,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", - "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz", + "integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.25.7", + "@babel/highlight": "^7.25.9", "picocolors": "^1.0.0" }, "engines": { @@ -94,30 +94,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", - "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.9.tgz", + "integrity": "sha512-yD+hEuJ/+wAJ4Ox2/rpNv5HIuPG82x3ZlQvYVn8iYCprdxzE7P1udpGF1jyjQVBU4dgznN+k2h103vxZ7NdPyw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", - "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.9.tgz", + "integrity": "sha512-WYvQviPw+Qyib0v92AwNIrdLISTp7RfDkM7bPqBvpbnhY4wq8HvHBZREVdYDXk98C8BkOIVnHAY3yvj7AVISxQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helpers": "^7.25.7", - "@babel/parser": "^7.25.8", - "@babel/template": "^7.25.7", - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.8", + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helpers": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -154,12 +154,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", - "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.9.tgz", + "integrity": "sha512-omlUGkr5EaoIJrhLf9CJ0TvjBRpd9+AXRG//0GEQ9THSo8wPiTlbpy1/Ow8ZTrbXpjd9FHXfbFQx32I04ht0FA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7", + "@babel/types": "^7.25.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -179,38 +179,38 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", - "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.7.tgz", - "integrity": "sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", + "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", - "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -244,17 +244,17 @@ "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz", - "integrity": "sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-member-expression-to-functions": "^7.25.7", - "@babel/helper-optimise-call-expression": "^7.25.7", - "@babel/helper-replace-supers": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", - "@babel/traverse": "^7.25.7", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -274,12 +274,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.7.tgz", - "integrity": "sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", + "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-annotate-as-pure": "^7.25.9", "regexpu-core": "^6.1.1", "semver": "^6.3.1" }, @@ -328,41 +328,41 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz", - "integrity": "sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", - "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", - "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.9.tgz", + "integrity": "sha512-TvLZY/F3+GvdRYFZFyxMvnsKi+4oJdgZzU3BoGN9Uc2d9C6zfNwJcKKhjqLAhK8i46mv93jsO74fDh3ih6rpHA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.7", - "@babel/helper-simple-access": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -372,35 +372,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz", - "integrity": "sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", - "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.7.tgz", - "integrity": "sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-wrap-function": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -410,14 +410,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz", - "integrity": "sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.7", - "@babel/helper-optimise-call-expression": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -427,92 +427,92 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", - "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz", - "integrity": "sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", - "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", - "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", - "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.7.tgz", - "integrity": "sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.7", - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", - "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.9.tgz", + "integrity": "sha512-oKWp3+usOJSzDZOucZUAMayhPz/xVjzymyDzUN8dk0Wd3RWMlGLXi07UCQ/CgQVb8LvXx3XBajJH4XGgkt7H7g==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", - "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -593,12 +593,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", - "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", + "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.8" + "@babel/types": "^7.25.9" }, "bin": { "parser": "bin/babel-parser.js" @@ -608,14 +608,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.7.tgz", - "integrity": "sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -625,13 +625,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.7.tgz", - "integrity": "sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -641,13 +641,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz", - "integrity": "sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -657,15 +657,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.7.tgz", - "integrity": "sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", - "@babel/plugin-transform-optional-chaining": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -675,14 +675,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.7.tgz", - "integrity": "sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -728,15 +728,15 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.7.tgz", - "integrity": "sha512-q1mqqqH0e1lhmsEQHV5U8OmdueBC2y0RFr2oUzZoFRtN3MvPmt2fsFRcNQAoGLTSNdHBFUYGnlgcRFhkBbKjPw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-decorators": "^7.25.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -746,12 +746,12 @@ } }, "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.8.tgz", - "integrity": "sha512-5SLPHA/Gk7lNdaymtSVS9jH77Cs7yuHTR3dYj+9q+M7R7tNLXhNuvnmOfafRIzpWL+dtMibuu1I4ofrc768Gkw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz", + "integrity": "sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -893,13 +893,13 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.7.tgz", - "integrity": "sha512-oXduHo642ZhstLVYTe2z2GSJIruU0c/W3/Ghr6A5yGMsVrvdnxO1z+3pbTcT7f3/Clnt+1z8D/w1r1f1SHaCHw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", + "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -921,12 +921,12 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.25.7.tgz", - "integrity": "sha512-LRUCsC0YucSjabsmxx6yly8+Q/5mxKdp9gemlpR9ro3bfpcOQOXx/CHivs7QCbjgygd6uQ2GcRfHu1FVax/hgg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.25.9.tgz", + "integrity": "sha512-9MhJ/SMTsVqsd69GyQg89lYR4o9T+oDGv5F6IsigxxqFVOyR/IflDLYP8WDI1l8fkhNGGktqkvL5qwNCtGEpgQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -936,12 +936,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.25.7.tgz", - "integrity": "sha512-fyoj6/YdVtlv2ROig/J0fP7hh/wNO1MJGm1NR70Pg7jbkF+jOUL9joorqaCOQh06Y+LfgTagHzC8KqZ3MF782w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.25.9.tgz", + "integrity": "sha512-F3FVgxwamIRS3+kfjNaPARX0DSAiH1exrQUVajXiR34hkdA9eyK+8rJbnu55DQjKL/ayuXqjNr2HDXwBEMEtFQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -951,13 +951,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz", - "integrity": "sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.9.tgz", + "integrity": "sha512-4GHX5uzr5QMOOuzV0an9MFju4hKlm0OyePl/lHhcsTVae5t/IKVHnb8W67Vr6FuLlk5lPqLB7n7O+K5R46emYg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -967,13 +967,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", - "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.9.tgz", + "integrity": "sha512-u3EN9ub8LyYvgTnrgp8gboElouayiwPdnM7x5tcnW3iSt09/lQYPwMNK40I9IUxo7QOZhAsPHCmmuO7EPdruqg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -983,12 +983,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz", - "integrity": "sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1071,12 +1071,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.7.tgz", - "integrity": "sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1103,12 +1103,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.7.tgz", - "integrity": "sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1118,15 +1118,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.8.tgz", - "integrity": "sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-remap-async-to-generator": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1136,14 +1136,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.7.tgz", - "integrity": "sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-remap-async-to-generator": "^7.25.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1153,13 +1153,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.7.tgz", - "integrity": "sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1169,12 +1169,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz", - "integrity": "sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1184,14 +1184,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.7.tgz", - "integrity": "sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1201,14 +1201,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.8.tgz", - "integrity": "sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.9.tgz", + "integrity": "sha512-UIf+72C7YJ+PJ685/PpATbCz00XqiFEzHX5iysRwfvNT0Ko+FaXSvRgLytFSp8xUItrG9pFM/KoBBZDrY/cYyg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1218,16 +1218,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.7.tgz", - "integrity": "sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-replace-supers": "^7.25.7", - "@babel/traverse": "^7.25.7", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -1247,13 +1247,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.7.tgz", - "integrity": "sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/template": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1263,12 +1263,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.7.tgz", - "integrity": "sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1278,14 +1278,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.7.tgz", - "integrity": "sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1295,13 +1295,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.7.tgz", - "integrity": "sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1311,14 +1311,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.7.tgz", - "integrity": "sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1328,13 +1328,13 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.8.tgz", - "integrity": "sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1344,13 +1344,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.7.tgz", - "integrity": "sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", + "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1360,12 +1360,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.8.tgz", - "integrity": "sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1375,13 +1375,13 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.7.tgz", - "integrity": "sha512-q8Td2PPc6/6I73g96SreSUCKEcwMXCwcXSIAVTyTTN6CpJe0dMj8coxu1fg1T9vfBLi6Rsi6a4ECcFBbKabS5w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.9.tgz", + "integrity": "sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-flow": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-flow": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1391,13 +1391,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.7.tgz", - "integrity": "sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1407,14 +1407,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.7.tgz", - "integrity": "sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1424,13 +1424,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.8.tgz", - "integrity": "sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1440,12 +1440,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.7.tgz", - "integrity": "sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1455,13 +1455,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.8.tgz", - "integrity": "sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1471,13 +1471,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.7.tgz", - "integrity": "sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1487,14 +1487,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.7.tgz", - "integrity": "sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1504,14 +1504,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", - "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-simple-access": "^7.25.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1521,16 +1521,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.7.tgz", - "integrity": "sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1540,14 +1540,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.7.tgz", - "integrity": "sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1557,13 +1557,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.7.tgz", - "integrity": "sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1573,13 +1573,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.7.tgz", - "integrity": "sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1589,13 +1589,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.8.tgz", - "integrity": "sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1605,13 +1605,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.8.tgz", - "integrity": "sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1621,12 +1621,12 @@ } }, "node_modules/@babel/plugin-transform-object-assign": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.25.7.tgz", - "integrity": "sha512-snTWKDjknsLh7l67henNYebPZ809tYTAunlSkPHu0upP70ehLMCHnozh4Dpq7OD2e7iYxhy560iqP+FlU8c2uQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.25.9.tgz", + "integrity": "sha512-I/Vl1aQnPsrrn837oLbo+VQtkNcjuuiATqwmuweg4fTauwHHQoxyjmjjOVKyO8OaTxgqYTKW3LuQsykXjDf5Ag==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1636,14 +1636,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.8.tgz", - "integrity": "sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-transform-parameters": "^7.25.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1653,14 +1653,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.7.tgz", - "integrity": "sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-replace-supers": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1670,13 +1670,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.8.tgz", - "integrity": "sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1686,14 +1686,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz", - "integrity": "sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1703,12 +1703,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.7.tgz", - "integrity": "sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1718,13 +1718,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", - "integrity": "sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1734,14 +1734,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.8.tgz", - "integrity": "sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1751,13 +1751,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.7.tgz", - "integrity": "sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1767,12 +1767,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.7.tgz", - "integrity": "sha512-r0QY7NVU8OnrwE+w2IWiRom0wwsTbjx4+xH2RTd7AVdof3uurXOF+/mXHQDRk+2jIvWgSaCHKMgggfvM4dyUGA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1782,16 +1782,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.7.tgz", - "integrity": "sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-module-imports": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-jsx": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1801,13 +1801,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.7.tgz", - "integrity": "sha512-5yd3lH1PWxzW6IZj+p+Y4OLQzz0/LzlOG8vGqonHfVR3euf1vyzyMUJk9Ac+m97BH46mFc/98t9PmYLyvgL3qg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.7" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1817,12 +1817,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz", - "integrity": "sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1832,12 +1832,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz", - "integrity": "sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1847,14 +1847,14 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.7.tgz", - "integrity": "sha512-6YTHJ7yjjgYqGc8S+CbEXhLICODk0Tn92j+vNJo07HFk9t3bjFgAKxPLFhHwF2NjmQVSI1zBRfBWUeVBa2osfA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1864,12 +1864,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.7.tgz", - "integrity": "sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1880,13 +1880,13 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.7.tgz", - "integrity": "sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1896,13 +1896,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.7.tgz", - "integrity": "sha512-Y9p487tyTzB0yDYQOtWnC+9HGOuogtP3/wNpun1xJXEEvI6vip59BSBTsHnekZLqxmPcgsrAKt46HAAb//xGhg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", @@ -1925,12 +1925,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.7.tgz", - "integrity": "sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1940,13 +1940,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.7.tgz", - "integrity": "sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1956,12 +1956,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.7.tgz", - "integrity": "sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1971,12 +1971,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.7.tgz", - "integrity": "sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1986,13 +1986,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.7.tgz", - "integrity": "sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2002,16 +2002,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.7.tgz", - "integrity": "sha512-VKlgy2vBzj8AmEzunocMun2fF06bsSWV+FvVXohtL6FGve/+L217qhHxRTVGHEDO/YR8IANcjzgJsd04J8ge5Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", + "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.7", - "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", - "@babel/plugin-syntax-typescript": "^7.25.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2021,13 +2021,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.7.tgz", - "integrity": "sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2037,14 +2037,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.7.tgz", - "integrity": "sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2054,13 +2054,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.7.tgz", - "integrity": "sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2070,14 +2070,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.7.tgz", - "integrity": "sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2087,74 +2087,74 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.8.tgz", - "integrity": "sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.9.tgz", + "integrity": "sha512-XqDEt+hfsQukahSX9JOBDHhpUHDhj2zGSxoqWQFCMajOSBnbhBdgON/bU/5PkBA1yX5tqW6tTzuIPVsZTQ7h5Q==", "license": "MIT", "peer": true, "dependencies": { - "@babel/compat-data": "^7.25.8", - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.7", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.7", + "@babel/compat-data": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.25.7", - "@babel/plugin-syntax-import-attributes": "^7.25.7", + "@babel/plugin-syntax-import-assertions": "^7.25.9", + "@babel/plugin-syntax-import-attributes": "^7.25.9", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.8", - "@babel/plugin-transform-async-to-generator": "^7.25.7", - "@babel/plugin-transform-block-scoped-functions": "^7.25.7", - "@babel/plugin-transform-block-scoping": "^7.25.7", - "@babel/plugin-transform-class-properties": "^7.25.7", - "@babel/plugin-transform-class-static-block": "^7.25.8", - "@babel/plugin-transform-classes": "^7.25.7", - "@babel/plugin-transform-computed-properties": "^7.25.7", - "@babel/plugin-transform-destructuring": "^7.25.7", - "@babel/plugin-transform-dotall-regex": "^7.25.7", - "@babel/plugin-transform-duplicate-keys": "^7.25.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.7", - "@babel/plugin-transform-dynamic-import": "^7.25.8", - "@babel/plugin-transform-exponentiation-operator": "^7.25.7", - "@babel/plugin-transform-export-namespace-from": "^7.25.8", - "@babel/plugin-transform-for-of": "^7.25.7", - "@babel/plugin-transform-function-name": "^7.25.7", - "@babel/plugin-transform-json-strings": "^7.25.8", - "@babel/plugin-transform-literals": "^7.25.7", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.8", - "@babel/plugin-transform-member-expression-literals": "^7.25.7", - "@babel/plugin-transform-modules-amd": "^7.25.7", - "@babel/plugin-transform-modules-commonjs": "^7.25.7", - "@babel/plugin-transform-modules-systemjs": "^7.25.7", - "@babel/plugin-transform-modules-umd": "^7.25.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.7", - "@babel/plugin-transform-new-target": "^7.25.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.8", - "@babel/plugin-transform-numeric-separator": "^7.25.8", - "@babel/plugin-transform-object-rest-spread": "^7.25.8", - "@babel/plugin-transform-object-super": "^7.25.7", - "@babel/plugin-transform-optional-catch-binding": "^7.25.8", - "@babel/plugin-transform-optional-chaining": "^7.25.8", - "@babel/plugin-transform-parameters": "^7.25.7", - "@babel/plugin-transform-private-methods": "^7.25.7", - "@babel/plugin-transform-private-property-in-object": "^7.25.8", - "@babel/plugin-transform-property-literals": "^7.25.7", - "@babel/plugin-transform-regenerator": "^7.25.7", - "@babel/plugin-transform-reserved-words": "^7.25.7", - "@babel/plugin-transform-shorthand-properties": "^7.25.7", - "@babel/plugin-transform-spread": "^7.25.7", - "@babel/plugin-transform-sticky-regex": "^7.25.7", - "@babel/plugin-transform-template-literals": "^7.25.7", - "@babel/plugin-transform-typeof-symbol": "^7.25.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.7", - "@babel/plugin-transform-unicode-property-regex": "^7.25.7", - "@babel/plugin-transform-unicode-regex": "^7.25.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.7", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.25.9", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", @@ -2180,14 +2180,14 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.7.tgz", - "integrity": "sha512-q2x3g0YHzo/Ohsr51KOYS/BtZMsvkzVd8qEyhZAyTatYdobfgXCuyppTqTuIhdq5kR/P3nyyVvZ6H5dMc4PnCQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz", + "integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", - "@babel/plugin-transform-flow-strip-types": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2212,18 +2212,18 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.7.tgz", - "integrity": "sha512-GjV0/mUEEXpi1U5ZgDprMRRgajGMRW3G5FjMr5KLKD8nT2fTG8+h/klV3+6Dm5739QE+K5+2e91qFKAYI3pmRg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz", + "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", - "@babel/plugin-transform-react-display-name": "^7.25.7", - "@babel/plugin-transform-react-jsx": "^7.25.7", - "@babel/plugin-transform-react-jsx-development": "^7.25.7", - "@babel/plugin-transform-react-pure-annotations": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2233,16 +2233,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.25.7.tgz", - "integrity": "sha512-rkkpaXJZOFN45Fb+Gki0c+KMIglk4+zZXOoMJuyEK8y8Kkc8Jd3BDmP7qPsz0zQMJj+UD7EprF+AqAXcILnexw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.25.9.tgz", + "integrity": "sha512-XWxw1AcKk36kgxf4C//fl0ikjLeqGUWn062/Fd8GtpTfDJOX6Ud95FK+4JlDA36BX4bNGndXi3a6Vr4Jo5/61A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-validator-option": "^7.25.7", - "@babel/plugin-syntax-jsx": "^7.25.7", - "@babel/plugin-transform-modules-commonjs": "^7.25.7", - "@babel/plugin-transform-typescript": "^7.25.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2252,9 +2252,9 @@ } }, "node_modules/@babel/register": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.7.tgz", - "integrity": "sha512-qHTd2Rhn/rKhSUwdY6+n98FmwXN+N+zxSVx3zWqRe9INyvTpv+aQ5gDV2+43ACd3VtMBzPPljbb0gZb8u5ma6Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", + "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", @@ -2389,9 +2389,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", - "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", + "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2401,30 +2401,30 @@ } }, "node_modules/@babel/template": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", - "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", - "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/template": "^7.25.7", - "@babel/types": "^7.25.7", + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2442,14 +2442,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", - "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", + "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2794,9 +2793,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-filetypes": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.7.tgz", - "integrity": "sha512-/DN0Ujp9/EXvpTcgih9JmBaE8n+G0wtsspyNdvHT5luRfpfol1xm/CIQb6xloCXCiLkWX+EMPeLSiVIZq+24dA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.8.tgz", + "integrity": "sha512-D3N8sm/iptzfVwsib/jvpX+K/++rM8SRpLDFUaM4jxm8EyGmSIYRbKZvdIv5BkAWmMlTWoRqlLn7Yb1b11jKJg==", "dev": true, "license": "MIT" }, @@ -2864,9 +2863,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-html": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.9.tgz", - "integrity": "sha512-BNp7w3m910K4qIVyOBOZxHuFNbVojUY6ES8Y8r7YjYgJkm2lCuQoVwwhPjurnomJ7BPmZTb+3LLJ58XIkgF7JQ==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.10.tgz", + "integrity": "sha512-I9uRAcdtHbh0wEtYZlgF0TTcgH0xaw1B54G2CW+tx4vHUwlde/+JBOfIzird4+WcMv4smZOfw+qHf7puFUbI5g==", "dev": true, "license": "MIT" }, @@ -3042,9 +3041,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-typescript": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.10.tgz", - "integrity": "sha512-7Zek3w4Rh3ZYyhihJ34FdnUBwP3OmRldnEq3hZ+FgQ0PyYZjXv5ztEViRBBxXjiFx1nHozr6pLi74TxToD8xsg==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.11.tgz", + "integrity": "sha512-FwvK5sKbwrVpdw0e9+1lVTl8FPoHYvfHRuQRQz2Ql5XkC0gwPPkpoyD1zYImjIyZRoYXk3yp9j8ss4iz7A7zoQ==", "dev": true, "license": "MIT" }, @@ -5430,32 +5429,18 @@ "license": "MIT" }, "node_modules/@libp2p/bootstrap": { - "version": "11.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-11.0.8.tgz", - "integrity": "sha512-eqcXQjfaoDEYAlTpe6sGFbPOe702D7ic6pYaVkGoJFW7hCMOrhX/pvSXSc9TQz1jrtH5ue31JokiblWJ1QNtag==", + "version": "11.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-11.0.9.tgz", + "integrity": "sha512-0c//fq1LkYW/2CFxXTz0Z8/hisGl38Zk7LaYePJe41E6F69Bh0akDvG7rAVPGwTutw3rv0Pj2W2MsSl8woZAyA==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", - "@libp2p/interface-internal": "^2.0.8", - "@libp2p/peer-id": "^5.0.5", + "@libp2p/interface-internal": "^2.0.9", + "@libp2p/peer-id": "^5.0.6", "@multiformats/mafmt": "^12.1.6", "@multiformats/multiaddr": "^12.2.3" } }, - "node_modules/@libp2p/bootstrap/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/crypto": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.0.5.tgz", @@ -5472,32 +5457,18 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/crypto/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/identify": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-3.0.8.tgz", - "integrity": "sha512-dQF+Cc2m1uX4YTlI9IPB8tMwvpWOWcFVl265JNYtuPJrU+VcprJKkSewd4g5jzPpb7wdVLoDQkzplQfbQmdHeQ==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-3.0.9.tgz", + "integrity": "sha512-VJjYDoOmgjw0i6xIMQxeG4B+ejzhTXkvSQ1orAhjm/aPlX542kcgAQmR9ZXiPT9OYROliNe6i4bS1MepDkO6sg==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.0.5", "@libp2p/interface": "^2.1.3", - "@libp2p/interface-internal": "^2.0.8", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/peer-record": "^8.0.8", - "@libp2p/utils": "^6.1.1", + "@libp2p/interface-internal": "^2.0.9", + "@libp2p/peer-id": "^5.0.6", + "@libp2p/peer-record": "^8.0.9", + "@libp2p/utils": "^6.1.2", "@multiformats/multiaddr": "^12.2.3", "@multiformats/multiaddr-matcher": "^1.2.1", "it-drain": "^3.0.7", @@ -5509,7 +5480,7 @@ "wherearewe": "^2.0.1" } }, - "node_modules/@libp2p/identify/node_modules/@libp2p/interface": { + "node_modules/@libp2p/interface": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", @@ -5523,34 +5494,20 @@ "uint8arraylist": "^2.4.8" } }, - "node_modules/@libp2p/interface": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.0.1.tgz", - "integrity": "sha512-zDAgu+ZNiYZxVsmcvCeNCLMnGORwLMMI8w0k2YcHwolATsv2q7QG3KpakmyKjH4m7C0hT86lGgf1sgGobPssYA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/interface-compliance-tests": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@libp2p/interface-compliance-tests/-/interface-compliance-tests-6.1.6.tgz", - "integrity": "sha512-Joxm41kLCZTYVNMW5APJkXWDR7tWL+9nFDMvxfvZTI5RoXuR5S7qLzdpTFMfsTjUBRu7ZcejT6jSfg//9lczLw==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@libp2p/interface-compliance-tests/-/interface-compliance-tests-6.1.7.tgz", + "integrity": "sha512-hXX8DuJ3dBf1IzkhbdM3/PUMbRzfp/cqbd9k5Hc9QERG0XVygbTnZaZQuEL0Oeu/UtSU92ZUXbGkVn0Qxe9Q1w==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.0.5", "@libp2p/interface": "^2.1.3", - "@libp2p/interface-internal": "^2.0.8", - "@libp2p/logger": "^5.1.1", - "@libp2p/multistream-select": "^6.0.6", - "@libp2p/peer-collections": "^6.0.8", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/utils": "^6.1.1", + "@libp2p/interface-internal": "^2.0.9", + "@libp2p/logger": "^5.1.2", + "@libp2p/multistream-select": "^6.0.7", + "@libp2p/peer-collections": "^6.0.9", + "@libp2p/peer-id": "^5.0.6", + "@libp2p/utils": "^6.1.2", "@multiformats/multiaddr": "^12.2.3", "abortable-iterator": "^5.0.1", "aegir": "^44.0.1", @@ -5578,20 +5535,6 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/interface-compliance-tests/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/interface-compliance-tests/node_modules/p-limit": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", @@ -5620,36 +5563,22 @@ } }, "node_modules/@libp2p/interface-internal": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-2.0.8.tgz", - "integrity": "sha512-yWAVuygiy2XhZK2UsOfy3iA30Bi78VeJDac6cAD/FQzu3rmGy2LNYtHuz1Vze9/OL4I6cseMNTGkozTeDg8nMg==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-2.0.9.tgz", + "integrity": "sha512-imXoOdKvvaQJLcmkv2ffVY9CyNbktsY2cN7aGRg8Dr1t9C7c/EDhSEfWcRQkxMAVib4jy/KnyL6JZKv5gWPu3g==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", - "@libp2p/peer-collections": "^6.0.8", - "@multiformats/multiaddr": "^12.2.3", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/@libp2p/interface-internal/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { + "@libp2p/peer-collections": "^6.0.9", "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", "progress-events": "^1.0.0", "uint8arraylist": "^2.4.8" } }, "node_modules/@libp2p/logger": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-5.1.1.tgz", - "integrity": "sha512-+pwFFZekKQHKdSrGURKZjfAJ86soc1e4HsI0r7dJN+kHICzKFzC+x5hM5GsWCorNj3y++xshWlF/n03zyxoyJQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-5.1.2.tgz", + "integrity": "sha512-To14ikSC+fnNXO+GkZB/Vj+kOGbdGcdpHdAMxvAWjm69ILDreGUlcDVotnAKVtN2bPAHL3Z0XzqDyKeRI6j73A==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", @@ -5659,28 +5588,14 @@ "weald": "^1.0.2" } }, - "node_modules/@libp2p/logger/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/mplex": { - "version": "11.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-11.0.8.tgz", - "integrity": "sha512-JQ+o1Hqc/2H6dlwPwwTmXR7qRwjG90C+avTZ0X24GU25j3zEx/LQ6kb5jN2VH+KNCdq9++z5U6aHLtz2fxShFQ==", + "version": "11.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-11.0.9.tgz", + "integrity": "sha512-cqHY47Uq/ksoTyv1zEwoZoAaT+nfhYA0EUm1jhlYDxLDP5z/OmMEbJaJgS1LQXa55p5CKNWbGAajltWiryKzSw==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", - "@libp2p/utils": "^6.1.1", + "@libp2p/utils": "^6.1.2", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", @@ -5689,24 +5604,10 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/mplex/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/multistream-select": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-6.0.6.tgz", - "integrity": "sha512-NM6CmJZYOJcB5woggoZgpMEqW6Jeovd65PYKqgvYUr7CQMPWUczUQQ9I/NSUEnv9g+69Pk9HN2AoMq1XKD60MQ==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-6.0.7.tgz", + "integrity": "sha512-37CE4aKUlETR6FT/9yb3DXp0xMLSsz2k0Qh061IPO2WZDswwjcU8qm/ADWqEtwsvOldfoAF7pTH4FaXktC5cEw==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", @@ -5720,50 +5621,22 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/multistream-select/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/peer-collections": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-6.0.8.tgz", - "integrity": "sha512-/xaSvb45lydLibt7sb+Im1ohIGiMfOlz5wcxelEgxmvUd0QmvirZXM3eAavQ+xrxmvJSPEQDmWSP+851ohRlKQ==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-6.0.9.tgz", + "integrity": "sha512-SQ83XUrxPoBdIezCntbXlhNxeNEqBfu/JznquoKFLVCfoTQm0251F1tApys3liGX4l1VJaLGLhLNd6dAmDRTyQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/utils": "^6.1.1", + "@libp2p/peer-id": "^5.0.6", + "@libp2p/utils": "^6.1.2", "multiformats": "^13.2.2" } }, - "node_modules/@libp2p/peer-collections/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/peer-id": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.0.5.tgz", - "integrity": "sha512-+9aX4II0hjMgKcFX/TMWUHRu2wOXOkfV5jO2N5m/R91K+Kp4Tt4n1ceXHjrbwwz3k2IWl0xJOMYjrf9dhOZWAw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.0.6.tgz", + "integrity": "sha512-gWzWm/z9dsCxL9TiOPd4VmS0V3GKMSvPWGLuNEvSA2j8+aqTzZ7jjQrF/SJtAJygD0h5jxvUnC1q05YaQUsTNA==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.0.5", @@ -5772,30 +5645,16 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/peer-id/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/peer-record": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-8.0.8.tgz", - "integrity": "sha512-wYqVN13ZaC/cVdFaTR3+Plzv4lf/BNVSzZK11cSSo3MqinOWqFs38plw9OC1Mfne2x9HYHLGwhj2zE802itD0A==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-8.0.9.tgz", + "integrity": "sha512-Ixiha//G7oCzQXSXHyXhv4Xp4qzVGSZLKUWmgUuLpqnlHrVXcqPLmPx0vkOik3ndIGriYGj5Hi7zAfCG0BN4oQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.0.5", "@libp2p/interface": "^2.1.3", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/utils": "^6.1.1", + "@libp2p/peer-id": "^5.0.6", + "@libp2p/utils": "^6.1.2", "@multiformats/multiaddr": "^12.2.3", "multiformats": "^13.2.2", "protons-runtime": "^5.4.0", @@ -5804,31 +5663,17 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/peer-record/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/peer-store": { - "version": "11.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-11.0.8.tgz", - "integrity": "sha512-csn1LcnnyanDw2/WO25CqxZyEzkQyfSsYu2Letsc2Po4J/9qJk8lfRc/2ezggaDf8z1y2y25b/WWhil1E7aLVA==", + "version": "11.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-11.0.9.tgz", + "integrity": "sha512-gBuSXihGtxD2r/KylgcaVm0fv0hy30j74mWcmMokpwqwLH2aLReGN9QelubmXtH2cnaRafD66QutuHNORIgRlg==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.0.5", "@libp2p/interface": "^2.1.3", - "@libp2p/peer-collections": "^6.0.8", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/peer-record": "^8.0.8", + "@libp2p/peer-collections": "^6.0.9", + "@libp2p/peer-id": "^5.0.6", + "@libp2p/peer-record": "^8.0.9", "@multiformats/multiaddr": "^12.2.3", "interface-datastore": "^8.3.0", "it-all": "^3.0.6", @@ -5839,20 +5684,6 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/peer-store/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/ping": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-2.0.1.tgz", @@ -5869,17 +5700,17 @@ } }, "node_modules/@libp2p/pubsub": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-10.0.8.tgz", - "integrity": "sha512-kOAQ2I6WUDtt1H6NMQuS+bfmj/JSMeLVIjPArn7xOe6OKaiIggGFP1LEI3waKeGVzwu0HGzIoNVe7WlNuyhwmg==", + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/pubsub/-/pubsub-10.0.9.tgz", + "integrity": "sha512-8f6anvHz2BT3jvMrbkb3P1jlYluk6/0CiBM+9dO9JgItdANmF3uJk7FxZjOTmJAipxxnd7Gglno47AA3xnEEng==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/crypto": "^5.0.5", "@libp2p/interface": "^2.1.3", - "@libp2p/interface-internal": "^2.0.8", - "@libp2p/peer-collections": "^6.0.8", - "@libp2p/peer-id": "^5.0.5", - "@libp2p/utils": "^6.1.1", + "@libp2p/interface-internal": "^2.0.9", + "@libp2p/peer-collections": "^6.0.9", + "@libp2p/peer-id": "^5.0.6", + "@libp2p/utils": "^6.1.2", "it-length-prefixed": "^9.0.4", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", @@ -5889,30 +5720,16 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/pubsub/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/utils": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-6.1.1.tgz", - "integrity": "sha512-lpqNyyTx7ygIfXyU4eqDONW7c4oc8Gf1xjDahlOWcggqNhLWsC3/8zTmziKlY3PjTvzY0W37nDRPO1KiM1Sduw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-6.1.2.tgz", + "integrity": "sha512-zjzjcLz2UlX29I5xM8dJ+7IEYJ6zE91RMYf2O4lLKQue/JYUjtCyPdlX6lDG7KhgDyoyB+buN6PVutWlFQteLg==", "license": "Apache-2.0 OR MIT", "dependencies": { "@chainsafe/is-ip": "^2.0.2", "@libp2p/crypto": "^5.0.5", "@libp2p/interface": "^2.1.3", - "@libp2p/logger": "^5.1.1", + "@libp2p/logger": "^5.1.2", "@multiformats/multiaddr": "^12.2.3", "@sindresorhus/fnv1a": "^3.1.0", "@types/murmurhash3js-revisited": "^3.0.3", @@ -5933,28 +5750,14 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/utils/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/websockets": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-9.0.8.tgz", - "integrity": "sha512-kqvdoeCGqdMjhekTeUCbzAhhrLw+cSI6bD1GdW/MOK5c5MffZ63MTZW+dT5PIVB3MviwVTRP1ogHS1X5UqMwUA==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-9.0.9.tgz", + "integrity": "sha512-8Y3WR80H3mBTkHrVqrP7CqHjiCPEH2ZThHkHq6OY/BhT7CcISWl9mtTcfalQ92JSnMP4VgHd/IwAzSPlFXVe2g==", "license": "Apache-2.0 OR MIT", "dependencies": { "@libp2p/interface": "^2.1.3", - "@libp2p/utils": "^6.1.1", + "@libp2p/utils": "^6.1.2", "@multiformats/mafmt": "^12.1.6", "@multiformats/multiaddr": "^12.2.3", "@multiformats/multiaddr-to-uri": "^10.0.1", @@ -5967,20 +5770,6 @@ "ws": "^8.17.0" } }, - "node_modules/@libp2p/websockets/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/@libp2p/websockets/node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -6171,7 +5960,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", - "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" @@ -8476,9 +8264,9 @@ } }, "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/@types/node": { - "version": "18.19.57", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.57.tgz", - "integrity": "sha512-I2ioBd/IPrYDMv9UNR5NlPElOZ68QB7yY5V2EsLtSrTO0LM0PnCEFF9biLWHf5k+sIy4ohueCV9t4gk1AEdlVA==", + "version": "18.19.59", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.59.tgz", + "integrity": "sha512-vizm2EqwV/7Zay+A6J3tGl9Lhr7CjZe2HmWS988sefiEmsyP9CeXEleho6i4hJk/8UtZAo0bWN4QPZZr83RxvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8634,15 +8422,15 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", - "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", + "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "picomatch": "^4.0.2" }, "engines": { "node": ">=14.0.0" @@ -8656,6 +8444,19 @@ } } }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", @@ -9999,9 +9800,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.7.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz", - "integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==", + "version": "22.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", + "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -10095,9 +9896,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.57", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.57.tgz", - "integrity": "sha512-I2ioBd/IPrYDMv9UNR5NlPElOZ68QB7yY5V2EsLtSrTO0LM0PnCEFF9biLWHf5k+sIy4ohueCV9t4gk1AEdlVA==", + "version": "18.19.59", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.59.tgz", + "integrity": "sha512-vizm2EqwV/7Zay+A6J3tGl9Lhr7CjZe2HmWS988sefiEmsyP9CeXEleho6i4hJk/8UtZAo0bWN4QPZZr83RxvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11093,9 +10894,9 @@ } }, "node_modules/aegir/node_modules/@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "20.17.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.0.tgz", + "integrity": "sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -11581,9 +11382,9 @@ } }, "node_modules/allure-commandline": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.30.0.tgz", - "integrity": "sha512-qSaGG/I8P38q8gufnxvEog+IrIeCick+PJxxoTp5Vh/L8kviymIfv3A7fvYWziOqZ/oDywyOuNFMEfPZiiKwAA==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.31.0.tgz", + "integrity": "sha512-/gwfIeMlz030fK8ZML/jSEzTtXsSRnsdx9ppfXR0jHvG+cO0pI/wZhq7YoMYgGEL8iVURIY2lDJlDyzOfHeSkw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12218,15 +12019,6 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "license": "MIT" }, - "node_modules/async-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -12463,9 +12255,9 @@ } }, "node_modules/bare-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.1.tgz", - "integrity": "sha512-Vm8kAeOcfzHPTH8sq0tHBnUqYrkXdroaBVVylqFT4cF5wnMfKEIxxy2jIGu2zKVNl9P8MAP9XBWwXJ9N2+jfEw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.2.tgz", + "integrity": "sha512-EFZHSIBkDgSHIwj2l2QZfP4U5OcD4xFAOwhSb/vlr9PIqyGJGvB/nfClJbcnh3EY4jtPE4zsb5ztae96bVF79A==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -12841,9 +12633,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -12860,10 +12652,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -13040,7 +12832,6 @@ "version": "18.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", - "dev": true, "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", @@ -13064,7 +12855,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -13085,14 +12875,12 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/cacache/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -13108,7 +12896,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -13546,7 +13333,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -16132,9 +15918,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.41", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", - "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "version": "1.5.44", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.44.tgz", + "integrity": "sha512-Lz3POUa7wANQA8G+9btKAdH+cqkfWCBdkotvQZJVOqRXMYGm1tTD835Z01iCjWpEBf0RInPBWuPfzhGbxOCULw==", "license": "ISC" }, "node_modules/electron-window": { @@ -18738,7 +18524,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -19596,7 +19381,6 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", - "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -19609,7 +19393,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -19888,7 +19671,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -22339,20 +22121,6 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/libp2p/node_modules/@libp2p/interface": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.1.3.tgz", - "integrity": "sha512-t1i2LWcnTGJEr7fDMslA8wYwBzJP81QKBlrBHoGhXxqqpRQa9035roCh/Akuw5RUgjKE47/ezjuzo90aWsJB8g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, "node_modules/lighthouse-logger": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", @@ -23791,9 +23559,9 @@ } }, "node_modules/markdown-table": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", - "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", "funding": { "type": "github", @@ -25552,7 +25320,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -25565,7 +25332,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -25578,7 +25344,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -25591,7 +25356,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -25604,7 +25368,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -25617,7 +25380,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -25631,7 +25393,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -26079,9 +25840,9 @@ } }, "node_modules/nise/node_modules/@sinonjs/fake-timers": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.3.tgz", - "integrity": "sha512-golm/Sc4CqLV/ZalIP14Nre7zPgd8xG/S3nHULMTBHMX0llyTNhE1O6nrgbfvLX2o0y849CnLKdu8OE05Ztiiw==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.4.tgz", + "integrity": "sha512-wpUq+QiKxrWk7U2pdvNSY9fNX62/k+7eEdlQMO0A3rU8tQ+vvzY/WzBhMz+GbQlATXZlXWYQqFWNFcn1SVvThA==", "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" @@ -26475,7 +26236,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", - "dev": true, "license": "ISC", "dependencies": { "hosted-git-info": "^3.0.2", @@ -26488,14 +26248,12 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", - "dev": true, "license": "MIT" }, "node_modules/npm-package-arg/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver" @@ -26505,7 +26263,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", - "dev": true, "license": "ISC", "dependencies": { "builtins": "^1.0.3" @@ -30535,7 +30292,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -30555,7 +30311,6 @@ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "deprecated": "This package is no longer supported.", - "dev": true, "license": "ISC", "dependencies": { "os-homedir": "^1.0.0", @@ -33310,7 +33065,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==", - "dev": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" } @@ -36844,7 +36598,6 @@ "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -37560,7 +37313,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -37605,7 +37357,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -37618,7 +37369,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -37631,7 +37381,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -37641,7 +37390,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -38084,15 +37832,6 @@ "license": "BSD-3-Clause", "peer": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -38966,7 +38705,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" @@ -38979,7 +38717,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" @@ -40221,9 +39958,9 @@ "version": "0.0.33", "license": "MIT OR Apache-2.0", "dependencies": { - "@libp2p/ping": "^1.1.2", - "@waku/enr": "^0.0.26", - "@waku/interfaces": "0.0.27", + "@libp2p/ping": "2.0.1", + "@waku/enr": "^0.0.27", + "@waku/interfaces": "0.0.28", "@waku/proto": "0.0.8", "@waku/utils": "0.0.21", "debug": "^4.3.4", @@ -40329,6 +40066,8 @@ }, "packages/discovery/node_modules/@libp2p/peer-id": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.0.1.tgz", + "integrity": "sha512-HwoW7dQ/o4NQ+5PQThOzMK2OHMRicmTZxVuMjbjWcPNnNWb8x/5vwjzdEUfqXimHYdZTIpy2PMMq6Jf4zvculQ==", "dev": true, "license": "Apache-2.0 OR MIT", "dependencies": { @@ -40559,7 +40298,6 @@ "@waku/message-hash": "0.1.17", "@waku/proto": "^0.0.8", "@waku/utils": "0.0.21", - "async-mutex": "^0.5.0", "libp2p": "2.1.8" }, "devDependencies": { diff --git a/packages/tests/tests/filter/peer_management.spec.ts b/packages/tests/tests/filter/peer_management.spec.ts deleted file mode 100644 index 7f3525ca38..0000000000 --- a/packages/tests/tests/filter/peer_management.spec.ts +++ /dev/null @@ -1,323 +0,0 @@ -import { ISubscription, LightNode, SDKProtocolResult } from "@waku/interfaces"; -import { - createDecoder, - createEncoder, - DecodedMessage, - utf8ToBytes -} from "@waku/sdk"; -import { delay } from "@waku/utils"; -import { expect } from "chai"; -import { describe } from "mocha"; - -import { - afterEachCustom, - beforeEachCustom, - DefaultTestPubsubTopic, - DefaultTestShardInfo, - runMultipleNodes, - ServiceNode, - ServiceNodesFleet, - teardownNodesWithRedundancy -} from "../../src/index.js"; - -describe("Waku Filter: Peer Management: E2E", function () { - this.timeout(15000); - let waku: LightNode; - let serviceNodes: ServiceNodesFleet; - - const contentTopic = "/test"; - - const encoder = createEncoder({ - pubsubTopic: DefaultTestPubsubTopic, - contentTopic - }); - - const decoder = createDecoder(contentTopic, DefaultTestPubsubTopic); - - beforeEachCustom(this, async () => { - [serviceNodes, waku] = await runMultipleNodes( - this.ctx, - DefaultTestShardInfo, - { lightpush: true, filter: true }, - undefined, - 5 - ); - }); - - afterEachCustom(this, async () => { - await teardownNodesWithRedundancy(serviceNodes, waku); - }); - - it("Number of peers are maintained correctly", async function () { - const messages: DecodedMessage[] = []; - const { error, results } = await waku.filter.subscribe([decoder], (msg) => { - messages.push(msg); - }); - - if (error) { - throw error; - } - - const { successes, failures } = results; - - await waku.lightPush.send(encoder, { - payload: utf8ToBytes("Hello_World") - }); - - expect(successes.length).to.be.greaterThan(0); - expect(successes.length).to.be.equal(waku.filter.numPeersToUse); - - if (failures) { - expect(failures.length).to.equal(0); - } - }); - - it("Ping succeeds for all connected peers", async function () { - const { error, subscription } = await waku.filter.subscribe( - [decoder], - () => {} - ); - if (error) { - throw error; - } - const pingResult = await subscription.ping(); - expect(pingResult.successes.length).to.equal(waku.filter.numPeersToUse); - expect(pingResult.failures.length).to.equal(0); - }); - - it("Ping fails for unsubscribed peers", async function () { - const { error, subscription } = await waku.filter.subscribe( - [decoder], - () => {} - ); - if (error) { - throw error; - } - await subscription.unsubscribe([contentTopic]); - const pingResult = await subscription.ping(); - expect(pingResult.successes.length).to.equal(0); - expect(pingResult.failures.length).to.be.greaterThan(0); - }); - - it("Keep-alive pings maintain the connection", async function () { - const { error, subscription } = await waku.filter.subscribe( - [decoder], - () => {}, - undefined, - { keepAlive: 100 } - ); - if (error) { - throw error; - } - - await delay(1000); - - const pingResult = await subscription.ping(); - expect(pingResult.successes.length).to.equal(waku.filter.numPeersToUse); - expect(pingResult.failures.length).to.equal(0); - }); - - it("Renews peer on consistent ping failures", async function () { - const maxPingFailures = 3; - const { error, subscription } = await waku.filter.subscribe( - [decoder], - () => {}, - undefined, - { - pingsBeforePeerRenewed: maxPingFailures - } - ); - if (error) { - throw error; - } - - const disconnectedNodePeerId = waku.filter.connectedPeers[0].id; - await waku.connectionManager.dropConnection(disconnectedNodePeerId); - - // Ping multiple times to exceed max failures - for (let i = 0; i <= maxPingFailures; i++) { - await subscription.ping(); - await delay(100); - } - - const pingResult = await subscription.ping(); - expect(pingResult.successes.length).to.equal(waku.filter.numPeersToUse); - expect(pingResult.failures.length).to.equal(0); - - expect(waku.filter.connectedPeers.length).to.equal( - waku.filter.numPeersToUse - ); - expect( - waku.filter.connectedPeers.some((peer) => - peer.id.equals(disconnectedNodePeerId) - ) - ).to.eq(false); - }); - - it("Tracks peer failures correctly", async function () { - const maxPingFailures = 3; - const { error, subscription } = await waku.filter.subscribe( - [decoder], - () => {}, - undefined, - { - pingsBeforePeerRenewed: maxPingFailures - } - ); - if (error) { - throw error; - } - - const targetPeer = waku.filter.connectedPeers[0]; - await waku.connectionManager.dropConnection(targetPeer.id); - - for (let i = 0; i < maxPingFailures; i++) { - await subscription.ping(targetPeer.id); - } - - // At this point, the peer should not be renewed yet - expect( - waku.filter.connectedPeers.some((peer) => peer.id.equals(targetPeer.id)) - ).to.be.true; - - // One more failure should trigger renewal - await subscription.ping(targetPeer.id); - - // adds delay as renewal happens as an async operation in the bg - await delay(300); - - expect( - waku.filter.connectedPeers.some((peer) => peer.id.equals(targetPeer.id)) - ).to.eq(false); - expect(waku.filter.connectedPeers.length).to.equal( - waku.filter.numPeersToUse - ); - }); - - it("Maintains correct number of peers after multiple subscribe/unsubscribe cycles", async function () { - let subscription: ISubscription; - for (let i = 0; i < 3; i++) { - const { error, subscription: _subscription } = - await waku.filter.subscribe([decoder], () => {}); - if (error) { - throw error; - } - subscription = _subscription; - let pingResult = await subscription.ping(); - expect(pingResult.successes.length).to.equal(waku.filter.numPeersToUse); - - await subscription.unsubscribe([contentTopic]); - pingResult = await subscription.ping(); - expect(pingResult.failures.length).to.be.greaterThan(0); - await subscription.subscribe([decoder], () => {}); - } - - const finalPingResult = await subscription!.ping(); - expect(finalPingResult.successes.length).to.equal( - waku.filter.numPeersToUse - ); - }); - - it("Renews peer on consistent missed messages", async function () { - const [serviceNodes, waku] = await runMultipleNodes( - this.ctx, - DefaultTestShardInfo, - { lightpush: true, filter: true }, - undefined, - 2 - ); - const serviceNodesPeerIdStr = await Promise.all( - serviceNodes.nodes.map(async (node) => - (await node.getPeerId()).toString() - ) - ); - const nodeWithoutDiscovery = new ServiceNode("WithoutDiscovery"); - await nodeWithoutDiscovery.start({ lightpush: true, filter: true }); - const nodeWithouDiscoveryPeerIdStr = ( - await nodeWithoutDiscovery.getPeerId() - ).toString(); - await waku.dial(await nodeWithoutDiscovery.getMultiaddrWithId()); - - const messages: DecodedMessage[] = []; - const { error, results } = await waku.filter.subscribe([decoder], (msg) => { - messages.push(msg); - }); - if (error) { - throw error; - } - - const { successes } = results; - - expect(successes.length).to.be.greaterThan(0); - expect(successes.length).to.be.equal(waku.filter.numPeersToUse); - - const sendMessage: () => Promise = async () => - waku.lightPush.send(encoder, { - payload: utf8ToBytes("Hello_World") - }); - - await sendMessage(); - - successes - .map((peerId) => - [nodeWithouDiscoveryPeerIdStr, ...serviceNodesPeerIdStr].includes( - peerId.toString() - ) - ) - .forEach((isConnected) => expect(isConnected).to.eq(true)); - - // send 2 more messages - await sendMessage(); - await sendMessage(); - - expect(waku.filter.connectedPeers.length).to.equal(2); - }); - - it("Renews peer for Filter on peer:disconnect event", async function () { - this.timeout(30000); - - const messages: DecodedMessage[] = []; - const { error, subscription } = await waku.filter.subscribe( - [decoder], - (msg) => { - messages.push(msg); - } - ); - - if (error) { - throw error; - } - - const initialPeers = waku.filter.connectedPeers; - expect(initialPeers.length).to.equal(waku.filter.numPeersToUse); - - const peerToDisconnect = initialPeers[0]; - await waku.connectionManager.dropConnection(peerToDisconnect.id); - - await delay(5000); - - expect(waku.filter.connectedPeers.length).to.equal( - waku.filter.numPeersToUse - ); - - const stillConnected = waku.filter.connectedPeers.some((peer) => - peer.id.equals(peerToDisconnect.id) - ); - expect(stillConnected).to.be.false; - - await waku.lightPush.send(encoder, { - payload: utf8ToBytes("Hello after disconnect") - }); - - await delay(2000); - - expect(messages.length).to.equal(1); - expect(new TextDecoder().decode(messages[0].payload)).to.equal( - "Hello after disconnect" - ); - - const pingResult = await subscription.ping(); - expect(pingResult.successes.length).to.equal(waku.filter.numPeersToUse); - expect(pingResult.failures.length).to.equal(0); - }); -}); From 8a3337df8916da3f745bcf9e789ffa7104be48a5 Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 24 Oct 2024 17:47:44 +0200 Subject: [PATCH 11/22] update deps and lock --- package-lock.json | 37 ++++++++++++++++++++++++++++++++- packages/core/package.json | 1 + packages/discovery/package.json | 2 +- packages/sdk/package.json | 1 + 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 518f07b4c2..6126151686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5960,6 +5960,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" @@ -12832,6 +12833,7 @@ "version": "18.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", @@ -12855,6 +12857,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -12875,12 +12878,14 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/cacache/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -12896,6 +12901,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -13333,6 +13339,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -18524,6 +18531,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -19381,6 +19389,7 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -19393,6 +19402,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -19671,6 +19681,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -25320,6 +25331,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -25332,6 +25344,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -25344,6 +25357,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -25356,6 +25370,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -25368,6 +25383,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -25380,6 +25396,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -25393,6 +25410,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -26236,6 +26254,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", + "dev": true, "license": "ISC", "dependencies": { "hosted-git-info": "^3.0.2", @@ -26248,12 +26267,14 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", + "dev": true, "license": "MIT" }, "node_modules/npm-package-arg/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver" @@ -26263,6 +26284,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", + "dev": true, "license": "ISC", "dependencies": { "builtins": "^1.0.3" @@ -30292,6 +30314,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -30311,6 +30334,7 @@ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "dependencies": { "os-homedir": "^1.0.0", @@ -33065,6 +33089,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==", + "dev": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" } @@ -36598,6 +36623,7 @@ "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -37313,6 +37339,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -37357,6 +37384,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -37369,6 +37397,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -37381,6 +37410,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -37390,6 +37420,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -38705,6 +38736,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" @@ -38717,6 +38749,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" @@ -39971,6 +40004,7 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@libp2p/interface": "^2.1.3", "@libp2p/peer-id": "^5.0.1", "@multiformats/multiaddr": "^12.0.0", "@rollup/plugin-commonjs": "^25.0.7", @@ -40056,7 +40090,7 @@ "node": ">=20" }, "peerDependencies": { - "@libp2p/interface": "2.0.1" + "@libp2p/interface": "^2.1.3" }, "peerDependenciesMeta": { "@libp2p/interface": { @@ -40301,6 +40335,7 @@ "libp2p": "2.1.8" }, "devDependencies": { + "@libp2p/interface": "2.1.3", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.2.3", diff --git a/packages/core/package.json b/packages/core/package.json index f4677fe6ae..2bc992d5b6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -82,6 +82,7 @@ }, "devDependencies": { "@libp2p/peer-id": "^5.0.1", + "@libp2p/interface": "^2.1.3", "@multiformats/multiaddr": "^12.0.0", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.0", diff --git a/packages/discovery/package.json b/packages/discovery/package.json index d5eae3d778..e3a15698a7 100644 --- a/packages/discovery/package.json +++ b/packages/discovery/package.json @@ -80,7 +80,7 @@ "sinon": "^18.0.0" }, "peerDependencies": { - "@libp2p/interface": "2.0.1" + "@libp2p/interface": "^2.1.3" }, "peerDependenciesMeta": { "@libp2p/interface": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 96560a06e7..1d1521cad6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -76,6 +76,7 @@ "libp2p": "2.1.8" }, "devDependencies": { + "@libp2p/interface": "2.1.3", "@types/chai": "^4.3.11", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.0", From d4210c7d178e73840e8bf1bbd4efcb70265c350e Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 24 Oct 2024 17:53:08 +0200 Subject: [PATCH 12/22] remove old test for peerManager, fix check and spell --- .cspell.json | 1 + .../protocols/light_push/light_push.spec.ts | 5 +- .../sdk/src/protocols/peer_manager.spec.ts | 148 ------------------ 3 files changed, 5 insertions(+), 149 deletions(-) delete mode 100644 packages/sdk/src/protocols/peer_manager.spec.ts diff --git a/.cspell.json b/.cspell.json index 99da5df60e..1b262f3a0d 100644 --- a/.cspell.json +++ b/.cspell.json @@ -116,6 +116,7 @@ "upgrader", "vacp", "varint", + "weboko", "waku", "wakuconnect", "wakunode", diff --git a/packages/sdk/src/protocols/light_push/light_push.spec.ts b/packages/sdk/src/protocols/light_push/light_push.spec.ts index 1e0a24293c..7c695212b5 100644 --- a/packages/sdk/src/protocols/light_push/light_push.spec.ts +++ b/packages/sdk/src/protocols/light_push/light_push.spec.ts @@ -10,6 +10,8 @@ import { utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; import sinon from "sinon"; +import { PeerManager } from "../peer_manager.js"; + import { LightPush } from "./light_push.js"; const PUBSUB_TOPIC = "/waku/2/rs/1/4"; @@ -156,7 +158,8 @@ function mockLightPush(options: MockLightPushOptions): LightPush { return new LightPush( { configuredPubsubTopics: options.pubsubTopics || [PUBSUB_TOPIC] - } as ConnectionManager, + } as unknown as ConnectionManager, + {} as unknown as PeerManager, options.libp2p ); } diff --git a/packages/sdk/src/protocols/peer_manager.spec.ts b/packages/sdk/src/protocols/peer_manager.spec.ts deleted file mode 100644 index bea3bb0c6d..0000000000 --- a/packages/sdk/src/protocols/peer_manager.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Peer, PeerId } from "@libp2p/interface"; -import { ConnectionManager, LightPushCodec } from "@waku/core"; -import { BaseProtocol } from "@waku/core/lib/base_protocol"; -import { Logger } from "@waku/utils"; -import { expect } from "chai"; -import sinon from "sinon"; - -import { PeerManager } from "./peer_manager.js"; - -describe("PeerManager", () => { - let peerManager: PeerManager; - let mockConnectionManager: sinon.SinonStubbedInstance; - let mockCore: sinon.SinonStubbedInstance; - let mockLogger: any; - - beforeEach(() => { - mockConnectionManager = sinon.createStubInstance(ConnectionManager); - mockCore = sinon.createStubInstance(BaseProtocol); - mockLogger = { - info: sinon.stub(), - warn: sinon.stub(), - error: sinon.stub(), - debug: sinon.stub(), - extend: sinon.stub().returns({ - info: sinon.stub(), - warn: sinon.stub(), - error: sinon.stub(), - debug: sinon.stub() - }) - }; - - mockCore.multicodec = LightPushCodec; - - peerManager = new PeerManager( - mockConnectionManager as any, - mockCore as any, - mockLogger as Logger - ); - }); - - afterEach(() => { - sinon.restore(); - }); - - const createMockPeer = (id: string): Peer => - ({ - id: { - toString: () => id - } as PeerId - }) as Peer; - - describe("addPeer", () => { - it("should add a peer", async () => { - const peer = createMockPeer("peer1"); - await peerManager.addPeer(peer); - - expect(mockConnectionManager.attemptDial.calledWith(peer.id)).to.be.true; - expect( - mockLogger.info.calledWith(sinon.match(/Added and dialed peer: peer1/)) - ).to.be.true; - expect(await peerManager.getPeerCount()).to.equal(1); - }); - }); - - describe("removePeer", () => { - it("should remove a peer", async () => { - const peer = createMockPeer("peer1"); - await peerManager.addPeer(peer); - await peerManager.removePeer(peer.id); - - expect(mockLogger.info.calledWith(sinon.match(/Removed peer: peer1/))).to - .be.true; - expect(await peerManager.getPeerCount()).to.equal(0); - }); - }); - - describe("getPeerCount", () => { - it("should return the correct number of peers", async () => { - await peerManager.addPeer(createMockPeer("peer1")); - await peerManager.addPeer(createMockPeer("peer2")); - - const count = await peerManager.getPeerCount(); - expect(count).to.equal(2); - }); - }); - - describe("hasPeers", () => { - it("should return true when peers exist", async () => { - await peerManager.addPeer(createMockPeer("peer1")); - const result = await peerManager.hasPeers(); - expect(result).to.be.true; - }); - - it("should return false when no peers exist", async () => { - const result = await peerManager.hasPeers(); - expect(result).to.be.false; - }); - }); - - describe("removeExcessPeers", () => { - it("should remove the specified number of excess peers", async () => { - await peerManager.addPeer(createMockPeer("peer1")); - await peerManager.addPeer(createMockPeer("peer2")); - await peerManager.addPeer(createMockPeer("peer3")); - - await peerManager.removeExcessPeers(2); - - const count = await peerManager.getPeerCount(); - expect(count).to.equal(1); - expect(mockLogger.info.calledWith(`Removing 2 excess peer(s)`)).to.be - .true; - }); - }); - - describe("findAndAddPeers", () => { - it("should find and add new peers", async () => { - const newPeers = [createMockPeer("peer1"), createMockPeer("peer2")]; - mockCore.getPeers.resolves(newPeers); - - const addedPeers = await peerManager.findAndAddPeers(2); - - expect(addedPeers).to.have.lengthOf(2); - expect(mockConnectionManager.attemptDial.callCount).to.equal(2); - }); - - it("should not add existing peers", async () => { - const existingPeer = createMockPeer("existing"); - await peerManager.addPeer(existingPeer); - - const newPeers = [existingPeer, createMockPeer("new")]; - mockCore.getPeers.resolves(newPeers); - - const addedPeers = await peerManager.findAndAddPeers(2); - - expect(addedPeers).to.have.lengthOf(1); - expect(mockConnectionManager.attemptDial.callCount).to.equal(2); // Once for existing, once for new - }); - - it("should log when no additional peers are found", async () => { - mockCore.getPeers.resolves([]); - - await peerManager.findAndAddPeers(2); - - expect(mockLogger.warn.calledWith("No additional peers found")).to.be - .true; - }); - }); -}); From 994d05e7cf8e5abc63e2f9ed7c456cd8ad6e947e Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 29 Jan 2025 00:15:18 +0100 Subject: [PATCH 13/22] rename to getConnectedPeers --- packages/interfaces/src/waku.ts | 2 +- packages/sdk/src/waku/waku.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/interfaces/src/waku.ts b/packages/interfaces/src/waku.ts index 733fb5093e..2b19f84479 100644 --- a/packages/interfaces/src/waku.ts +++ b/packages/interfaces/src/waku.ts @@ -125,7 +125,7 @@ export interface IWaku { /** * @returns {Peer[]} an array of all connected peers */ - getPeers(): Promise; + getConnectedPeers(): Promise; } export interface LightNode extends IWaku { diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index 403ba2568c..bea192c202 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -206,7 +206,7 @@ export class WakuNode implements IWaku { await this.libp2p.stop(); } - public async getPeers(): Promise { + public async getConnectedPeers(): Promise { return this.connectionManager.getConnectedPeers(); } From 1be6e2d3ddbbd1d214120cc9c619537409e42388 Mon Sep 17 00:00:00 2001 From: Sasha <118575614+weboko@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:39:57 +0100 Subject: [PATCH 14/22] feat: improve filter subscriptions (#2193) * add message cache to Filter * remove WakuOptions and use only ProtocolCreateOptions * move subscribe options to createLightNode Fitler protocol options * rename SubscriptionManager to Subscription * rename to CreateNodeOptions * add warning * feat: introduce subscription manager (#2202) * feat: inroduce subscription manager * fix: make pipeline succeed (#2238) * fix test * use hardcoded value * update playwright * fix test:browser --- .github/workflows/ci.yml | 2 +- .github/workflows/playwright.yml | 2 +- packages/browser-tests/package.json | 2 +- packages/interfaces/src/filter.ts | 32 +- packages/interfaces/src/protocols.ts | 24 +- packages/relay/src/create.ts | 13 +- packages/relay/src/relay.ts | 4 +- packages/sdk/src/create/create.ts | 6 +- packages/sdk/src/create/libp2p.ts | 11 +- .../sdk/src/protocols/filter/constants.ts | 7 +- packages/sdk/src/protocols/filter/index.ts | 67 +-- .../sdk/src/protocols/filter/subscription.ts | 260 +++++++++++ .../protocols/filter/subscription_manager.ts | 441 ------------------ .../protocols/filter/subscription_monitor.ts | 287 ++++++++++++ packages/sdk/src/protocols/filter/utils.ts | 15 + packages/sdk/src/protocols/peer_manager.ts | 22 +- packages/sdk/src/reliability_monitor/index.ts | 56 --- .../sdk/src/reliability_monitor/receiver.ts | 185 -------- packages/sdk/src/waku/waku.ts | 23 +- packages/tests/src/lib/runNodes.ts | 8 +- packages/tests/src/utils/nodes.ts | 4 +- packages/tests/tests/filter/utils.ts | 4 +- .../tests/wait_for_remote_peer.node.spec.ts | 20 +- packages/tests/tests/waku.node.spec.ts | 5 +- .../utils/src/common/sharding/type_guards.ts | 6 +- 25 files changed, 701 insertions(+), 805 deletions(-) create mode 100644 packages/sdk/src/protocols/filter/subscription.ts delete mode 100644 packages/sdk/src/protocols/filter/subscription_manager.ts create mode 100644 packages/sdk/src/protocols/filter/subscription_monitor.ts create mode 100644 packages/sdk/src/protocols/filter/utils.ts delete mode 100644 packages/sdk/src/reliability_monitor/index.ts delete mode 100644 packages/sdk/src/reliability_monitor/receiver.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b92f2eb33f..49f5a52038 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: browser: runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.48.0-jammy + image: mcr.microsoft.com/playwright:v1.50.0-jammy env: HOME: "/root" steps: diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2caeab4c91..82009a92f7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,7 +20,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.48.0-jammy + image: mcr.microsoft.com/playwright:v1.50.0-jammy steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/packages/browser-tests/package.json b/packages/browser-tests/package.json index 81244df52c..3e9810b148 100644 --- a/packages/browser-tests/package.json +++ b/packages/browser-tests/package.json @@ -11,7 +11,7 @@ "test": "npx playwright test" }, "devDependencies": { - "@playwright/test": "^1.48.1", + "@playwright/test": "^1.50.0", "@waku/create-app": "^0.1.1-504bcd4", "dotenv-flow": "^4.1.0", "serve": "^14.2.3" diff --git a/packages/interfaces/src/filter.ts b/packages/interfaces/src/filter.ts index f992a497a7..7d5168e27b 100644 --- a/packages/interfaces/src/filter.ts +++ b/packages/interfaces/src/filter.ts @@ -15,17 +15,34 @@ export type SubscriptionCallback = { callback: Callback; }; -export type SubscribeOptions = { - keepAlive?: number; - pingsBeforePeerRenewed?: number; - enableLightPushFilterCheck?: boolean; +export type FilterProtocolOptions = { + /** + * Interval with which Filter subscription will attempt to send ping requests to subscribed peers. + * + * @default 60_000 + */ + keepAliveIntervalMs: number; + + /** + * Number of failed pings allowed to make to a remote peer before attempting to subscribe to a new one. + * + * @default 3 + */ + pingsBeforePeerRenewed: number; + + /** + * Enables js-waku to send probe LightPush message over subscribed pubsubTopics on created subscription. + * In case message won't be received back through Filter - js-waku will attempt to subscribe to another peer. + * + * @default false + */ + enableLightPushFilterCheck: boolean; }; export interface ISubscription { subscribe( decoders: IDecoder | IDecoder[], - callback: Callback, - options?: SubscribeOptions + callback: Callback ): Promise; unsubscribe(contentTopics: ContentTopic[]): Promise; @@ -38,8 +55,7 @@ export interface ISubscription { export type IFilter = IReceiver & { protocol: IBaseProtocolCore } & { subscribe( decoders: IDecoder | IDecoder[], - callback: Callback, - subscribeOptions?: SubscribeOptions + callback: Callback ): Promise; }; diff --git a/packages/interfaces/src/protocols.ts b/packages/interfaces/src/protocols.ts index 278d67c33e..40fc66542d 100644 --- a/packages/interfaces/src/protocols.ts +++ b/packages/interfaces/src/protocols.ts @@ -2,6 +2,7 @@ import type { Libp2p } from "@libp2p/interface"; import type { PeerId } from "@libp2p/interface"; import type { ConnectionManagerOptions } from "./connection_manager.js"; +import type { FilterProtocolOptions } from "./filter.js"; import type { CreateLibp2pOptions } from "./libp2p.js"; import type { IDecodedMessage } from "./message.js"; import { ThisAndThat, ThisOrThat } from "./misc.js"; @@ -23,19 +24,13 @@ export type IBaseProtocolCore = { export type NetworkConfig = StaticSharding | AutoSharding; -export type ProtocolCreateOptions = { +export type CreateNodeOptions = { /** - * Configuration for determining the network in use. - * - * If using Static Sharding: - * Default value is configured for The Waku Network. - * The format to specify a shard is: clusterId: number, shards: number[] - * To learn more about the sharding specification, see [Relay Sharding](https://rfc.vac.dev/spec/51/). + * Set the user agent string to be used in identification of the node. * - * If using Auto Sharding: - * See [Waku v2 Topic Usage Recommendations](https://github.com/vacp2p/rfc-index/blob/main/waku/informational/23/topics.md#content-topics) for details. - * You cannot add or remove content topics after initialization of the node. + * @default "js-waku" */ + userAgent?: string; /** * Configuration for determining the network in use. @@ -93,10 +88,17 @@ export type ProtocolCreateOptions = { bootstrapPeers?: string[]; /** - * Configuration for connection manager. If not specified - default values are applied. + * Configuration for connection manager. + * If not specified - default values are applied. */ connectionManager?: Partial; + /** + * Configuration for Filter protocol. + * If not specified - default values are applied. + */ + filter?: Partial; + /** * Options for the Store protocol. */ diff --git a/packages/relay/src/create.ts b/packages/relay/src/create.ts index 448598162b..476e71bf44 100644 --- a/packages/relay/src/create.ts +++ b/packages/relay/src/create.ts @@ -1,10 +1,5 @@ -import type { RelayNode } from "@waku/interfaces"; -import { - createLibp2pAndUpdateOptions, - CreateWakuNodeOptions, - WakuNode, - WakuOptions -} from "@waku/sdk"; +import type { CreateNodeOptions, RelayNode } from "@waku/interfaces"; +import { createLibp2pAndUpdateOptions, WakuNode } from "@waku/sdk"; import { RelayCreateOptions, wakuGossipSub, wakuRelay } from "./relay.js"; @@ -19,7 +14,7 @@ import { RelayCreateOptions, wakuGossipSub, wakuRelay } from "./relay.js"; * or use this function with caution. */ export async function createRelayNode( - options: CreateWakuNodeOptions & Partial + options: CreateNodeOptions & Partial ): Promise { options = { ...options, @@ -36,7 +31,7 @@ export async function createRelayNode( return new WakuNode( pubsubTopics, - options as WakuOptions, + options as CreateNodeOptions, libp2p, {}, relay diff --git a/packages/relay/src/relay.ts b/packages/relay/src/relay.ts index ed1cc0d556..8c7e0c1cac 100644 --- a/packages/relay/src/relay.ts +++ b/packages/relay/src/relay.ts @@ -11,6 +11,7 @@ import { sha256 } from "@noble/hashes/sha256"; import { ActiveSubscriptions, Callback, + CreateNodeOptions, IAsyncIterator, IDecodedMessage, IDecoder, @@ -18,7 +19,6 @@ import { IMessage, IRelay, Libp2p, - ProtocolCreateOptions, ProtocolError, PubsubTopic, SDKProtocolResult @@ -39,7 +39,7 @@ export type Observer = { callback: Callback; }; -export type RelayCreateOptions = ProtocolCreateOptions & GossipsubOpts; +export type RelayCreateOptions = CreateNodeOptions & GossipsubOpts; export type ContentTopic = string; /** diff --git a/packages/sdk/src/create/create.ts b/packages/sdk/src/create/create.ts index 155fee3403..ccd519e7bc 100644 --- a/packages/sdk/src/create/create.ts +++ b/packages/sdk/src/create/create.ts @@ -1,6 +1,6 @@ -import { type LightNode } from "@waku/interfaces"; +import type { CreateNodeOptions, LightNode } from "@waku/interfaces"; -import { CreateWakuNodeOptions, WakuNode } from "../waku/index.js"; +import { WakuNode } from "../waku/index.js"; import { createLibp2pAndUpdateOptions } from "./libp2p.js"; @@ -10,7 +10,7 @@ import { createLibp2pAndUpdateOptions } from "./libp2p.js"; * Uses Waku Filter V2 by default. */ export async function createLightNode( - options: CreateWakuNodeOptions = {} + options: CreateNodeOptions = {} ): Promise { const { libp2p, pubsubTopics } = await createLibp2pAndUpdateOptions(options); diff --git a/packages/sdk/src/create/libp2p.ts b/packages/sdk/src/create/libp2p.ts index 7aca089b2e..1b4afce675 100644 --- a/packages/sdk/src/create/libp2p.ts +++ b/packages/sdk/src/create/libp2p.ts @@ -8,6 +8,7 @@ import { all as filterAll, wss } from "@libp2p/websockets/filters"; import { wakuMetadata } from "@waku/core"; import { type CreateLibp2pOptions, + type CreateNodeOptions, DefaultNetworkConfig, type IMetadata, type Libp2p, @@ -18,11 +19,6 @@ import { derivePubsubTopicsFromNetworkConfig, Logger } from "@waku/utils"; import { createLibp2p } from "libp2p"; import { isTestEnvironment } from "../env.js"; -import { - CreateWakuNodeOptions, - DefaultPingMaxInboundStreams, - DefaultUserAgent -} from "../waku/index.js"; import { defaultPeerDiscoveries } from "./discovery.js"; @@ -32,6 +28,9 @@ type MetadataService = { const log = new Logger("sdk:create"); +const DefaultUserAgent = "js-waku"; +const DefaultPingMaxInboundStreams = 10; + export async function defaultLibp2p( pubsubTopics: PubsubTopic[], options?: Partial, @@ -79,7 +78,7 @@ export async function defaultLibp2p( } export async function createLibp2pAndUpdateOptions( - options: CreateWakuNodeOptions + options: CreateNodeOptions ): Promise<{ libp2p: Libp2p; pubsubTopics: PubsubTopic[] }> { const { networkConfig } = options; const pubsubTopics = derivePubsubTopicsFromNetworkConfig( diff --git a/packages/sdk/src/protocols/filter/constants.ts b/packages/sdk/src/protocols/filter/constants.ts index 9477a7e417..43fbf11374 100644 --- a/packages/sdk/src/protocols/filter/constants.ts +++ b/packages/sdk/src/protocols/filter/constants.ts @@ -1,8 +1,3 @@ export const DEFAULT_KEEP_ALIVE = 60_000; +export const DEFAULT_MAX_PINGS = 3; export const DEFAULT_LIGHT_PUSH_FILTER_CHECK = false; -export const DEFAULT_LIGHT_PUSH_FILTER_CHECK_INTERVAL = 10_000; - -export const DEFAULT_SUBSCRIBE_OPTIONS = { - keepAlive: DEFAULT_KEEP_ALIVE, - enableLightPushFilterCheck: DEFAULT_LIGHT_PUSH_FILTER_CHECK -}; diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index a70e484793..1e4b6d86bf 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -1,20 +1,19 @@ import { ConnectionManager, FilterCore } from "@waku/core"; -import { - type Callback, - type CreateSubscriptionResult, - type IAsyncIterator, - type IDecodedMessage, - type IDecoder, - type IFilter, - type ILightPush, - type Libp2p, - NetworkConfig, - ProtocolError, - type PubsubTopic, - type SubscribeOptions, +import type { + Callback, + CreateSubscriptionResult, + FilterProtocolOptions, + IAsyncIterator, + IDecodedMessage, + IDecoder, + IFilter, + ILightPush, + Libp2p, + PubsubTopic, SubscribeResult, - type Unsubscribe + Unsubscribe } from "@waku/interfaces"; +import { NetworkConfig, ProtocolError } from "@waku/interfaces"; import { ensurePubsubTopicIsConfigured, groupByContentTopic, @@ -25,22 +24,26 @@ import { import { PeerManager } from "../peer_manager.js"; -import { DEFAULT_SUBSCRIBE_OPTIONS } from "./constants.js"; -import { SubscriptionManager } from "./subscription_manager.js"; +import { Subscription } from "./subscription.js"; +import { buildConfig } from "./utils.js"; const log = new Logger("sdk:filter"); class Filter implements IFilter { public readonly protocol: FilterCore; - private activeSubscriptions = new Map(); + private readonly config: FilterProtocolOptions; + private activeSubscriptions = new Map(); public constructor( private connectionManager: ConnectionManager, private libp2p: Libp2p, private peerManager: PeerManager, - private lightPush?: ILightPush + private lightPush?: ILightPush, + config?: Partial ) { + this.config = buildConfig(config); + this.protocol = new FilterCore( async (pubsubTopic, wakuMessage, peerIdStr) => { const subscription = this.getActiveSubscription(pubsubTopic); @@ -50,6 +53,7 @@ class Filter implements IFilter { ); return; } + await subscription.processIncomingMessage(wakuMessage, peerIdStr); }, @@ -66,7 +70,6 @@ class Filter implements IFilter { * * @param {IDecoder | IDecoder[]} decoders - A single decoder or an array of decoders to use for decoding messages. * @param {Callback} callback - The callback function to be invoked with decoded messages. - * @param {SubscribeOptions} [subscribeOptions=DEFAULT_SUBSCRIBE_OPTIONS] - Options for the subscription. * * @returns {Promise} A promise that resolves to an object containing: * - subscription: The created subscription object if successful, or null if failed. @@ -100,8 +103,7 @@ class Filter implements IFilter { */ public async subscribe( decoders: IDecoder | IDecoder[], - callback: Callback, - subscribeOptions: SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS + callback: Callback ): Promise { const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); @@ -127,8 +129,7 @@ class Filter implements IFilter { const { failures, successes } = await subscription.subscribe( decoders, - callback, - subscribeOptions + callback ); return { subscription, @@ -173,12 +174,13 @@ class Filter implements IFilter { this.getActiveSubscription(pubsubTopic) ?? this.setActiveSubscription( pubsubTopic, - new SubscriptionManager( + new Subscription( pubsubTopic, this.protocol, this.connectionManager, this.peerManager, this.libp2p, + this.config, this.lightPush ) ); @@ -206,8 +208,7 @@ class Filter implements IFilter { */ public async subscribeWithUnsubscribe( decoders: IDecoder | IDecoder[], - callback: Callback, - options: SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS + callback: Callback ): Promise { const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); @@ -231,7 +232,7 @@ class Filter implements IFilter { throw Error(`Failed to create subscription: ${error}`); } - await subscription.subscribe(decoders, callback, options); + await subscription.subscribe(decoders, callback); const contentTopics = Array.from( groupByContentTopic( @@ -250,17 +251,16 @@ class Filter implements IFilter { return toAsyncIterator(this, decoders); } - //TODO: move to SubscriptionManager private getActiveSubscription( pubsubTopic: PubsubTopic - ): SubscriptionManager | undefined { + ): Subscription | undefined { return this.activeSubscriptions.get(pubsubTopic); } private setActiveSubscription( pubsubTopic: PubsubTopic, - subscription: SubscriptionManager - ): SubscriptionManager { + subscription: Subscription + ): Subscription { this.activeSubscriptions.set(pubsubTopic, subscription); return subscription; } @@ -285,8 +285,9 @@ class Filter implements IFilter { export function wakuFilter( connectionManager: ConnectionManager, peerManager: PeerManager, - lightPush?: ILightPush + lightPush?: ILightPush, + config?: Partial ): (libp2p: Libp2p) => IFilter { return (libp2p: Libp2p) => - new Filter(connectionManager, libp2p, peerManager, lightPush); + new Filter(connectionManager, libp2p, peerManager, lightPush, config); } diff --git a/packages/sdk/src/protocols/filter/subscription.ts b/packages/sdk/src/protocols/filter/subscription.ts new file mode 100644 index 0000000000..c585a05408 --- /dev/null +++ b/packages/sdk/src/protocols/filter/subscription.ts @@ -0,0 +1,260 @@ +import { ConnectionManager, createDecoder, FilterCore } from "@waku/core"; +import { + type Callback, + type ContentTopic, + type CoreProtocolResult, + FilterProtocolOptions, + type IDecodedMessage, + type IDecoder, + type ILightPush, + type IProtoMessage, + type ISubscription, + type Libp2p, + type PeerIdStr, + ProtocolError, + type PubsubTopic, + type SDKProtocolResult, + SubscriptionCallback +} from "@waku/interfaces"; +import { WakuMessage } from "@waku/proto"; +import { groupByContentTopic, Logger } from "@waku/utils"; + +import { PeerManager } from "../peer_manager.js"; + +import { SubscriptionMonitor } from "./subscription_monitor.js"; + +const log = new Logger("sdk:filter:subscription"); + +export class Subscription implements ISubscription { + private readonly monitor: SubscriptionMonitor; + + private subscriptionCallbacks: Map< + ContentTopic, + SubscriptionCallback + > = new Map(); + + public constructor( + private readonly pubsubTopic: PubsubTopic, + private readonly protocol: FilterCore, + connectionManager: ConnectionManager, + peerManager: PeerManager, + libp2p: Libp2p, + private readonly config: FilterProtocolOptions, + lightPush?: ILightPush + ) { + this.pubsubTopic = pubsubTopic; + + this.monitor = new SubscriptionMonitor({ + pubsubTopic, + config, + libp2p, + connectionManager, + filter: protocol, + peerManager, + lightPush, + activeSubscriptions: this.subscriptionCallbacks + }); + } + + public async subscribe( + decoders: IDecoder | IDecoder[], + callback: Callback + ): Promise { + const decodersArray = Array.isArray(decoders) ? decoders : [decoders]; + + // check that all decoders are configured for the same pubsub topic as this subscription + for (const decoder of decodersArray) { + if (decoder.pubsubTopic !== this.pubsubTopic) { + return { + failures: [ + { + error: ProtocolError.TOPIC_DECODER_MISMATCH + } + ], + successes: [] + }; + } + } + + if (this.config.enableLightPushFilterCheck) { + decodersArray.push( + createDecoder( + this.monitor.reservedContentTopic, + this.pubsubTopic + ) as IDecoder + ); + } + + const decodersGroupedByCT = groupByContentTopic(decodersArray); + const contentTopics = Array.from(decodersGroupedByCT.keys()); + + const peers = await this.monitor.getPeers(); + const promises = peers.map(async (peer) => { + return this.protocol.subscribe(this.pubsubTopic, peer, contentTopics); + }); + + const results = await Promise.allSettled(promises); + + const finalResult = this.handleResult(results, "subscribe"); + + // Save the callback functions by content topics so they + // can easily be removed (reciprocally replaced) if `unsubscribe` (reciprocally `subscribe`) + // is called for those content topics + decodersGroupedByCT.forEach((decoders, contentTopic) => { + // Cast the type because a given `subscriptionCallbacks` map may hold + // Decoder that decode to different implementations of `IDecodedMessage` + const subscriptionCallback = { + decoders, + callback + } as unknown as SubscriptionCallback; + + // don't handle case of internal content topic + if (contentTopic === this.monitor.reservedContentTopic) { + return; + } + + // The callback and decoder may override previous values, this is on + // purpose as the user may call `subscribe` to refresh the subscription + this.subscriptionCallbacks.set(contentTopic, subscriptionCallback); + }); + + this.monitor.start(); + + return finalResult; + } + + public async unsubscribe( + contentTopics: ContentTopic[] + ): Promise { + const peers = await this.monitor.getPeers(); + const promises = peers.map(async (peer) => { + const response = await this.protocol.unsubscribe( + this.pubsubTopic, + peer, + contentTopics + ); + + contentTopics.forEach((contentTopic: string) => { + this.subscriptionCallbacks.delete(contentTopic); + }); + + return response; + }); + + const results = await Promise.allSettled(promises); + const finalResult = this.handleResult(results, "unsubscribe"); + + if (this.subscriptionCallbacks.size === 0) { + this.monitor.stop(); + } + + return finalResult; + } + + public async ping(): Promise { + const peers = await this.monitor.getPeers(); + const promises = peers.map((peer) => this.protocol.ping(peer)); + + const results = await Promise.allSettled(promises); + return this.handleResult(results, "ping"); + } + + public async unsubscribeAll(): Promise { + const peers = await this.monitor.getPeers(); + const promises = peers.map(async (peer) => + this.protocol.unsubscribeAll(this.pubsubTopic, peer) + ); + + const results = await Promise.allSettled(promises); + + this.subscriptionCallbacks.clear(); + + const finalResult = this.handleResult(results, "unsubscribeAll"); + + this.monitor.stop(); + + return finalResult; + } + + public async processIncomingMessage( + message: WakuMessage, + peerIdStr: PeerIdStr + ): Promise { + const received = this.monitor.notifyMessageReceived( + peerIdStr, + message as IProtoMessage + ); + + if (received) { + log.info("Message already received, skipping"); + return; + } + + const { contentTopic } = message; + const subscriptionCallback = this.subscriptionCallbacks.get(contentTopic); + if (!subscriptionCallback) { + log.error("No subscription callback available for ", contentTopic); + return; + } + log.info( + "Processing message with content topic ", + contentTopic, + " on pubsub topic ", + this.pubsubTopic + ); + await pushMessage(subscriptionCallback, this.pubsubTopic, message); + } + + private handleResult( + results: PromiseSettledResult[], + type: "ping" | "subscribe" | "unsubscribe" | "unsubscribeAll" + ): SDKProtocolResult { + const result: SDKProtocolResult = { failures: [], successes: [] }; + + for (const promiseResult of results) { + if (promiseResult.status === "rejected") { + log.error( + `Failed to resolve ${type} promise successfully: `, + promiseResult.reason + ); + result.failures.push({ error: ProtocolError.GENERIC_FAIL }); + } else { + const coreResult = promiseResult.value; + if (coreResult.failure) { + result.failures.push(coreResult.failure); + } else { + result.successes.push(coreResult.success); + } + } + } + return result; + } +} + +async function pushMessage( + subscriptionCallback: SubscriptionCallback, + pubsubTopic: PubsubTopic, + message: WakuMessage +): Promise { + const { decoders, callback } = subscriptionCallback; + + const { contentTopic } = message; + if (!contentTopic) { + log.warn("Message has no content topic, skipping"); + return; + } + + try { + const decodePromises = decoders.map((dec) => + dec + .fromProtoObj(pubsubTopic, message as IProtoMessage) + .then((decoded) => decoded || Promise.reject("Decoding failed")) + ); + + const decodedMessage = await Promise.any(decodePromises); + + await callback(decodedMessage); + } catch (e) { + log.error("Error decoding message", e); + } +} diff --git a/packages/sdk/src/protocols/filter/subscription_manager.ts b/packages/sdk/src/protocols/filter/subscription_manager.ts deleted file mode 100644 index 59af175121..0000000000 --- a/packages/sdk/src/protocols/filter/subscription_manager.ts +++ /dev/null @@ -1,441 +0,0 @@ -import type { Peer } from "@libp2p/interface"; -import type { PeerId } from "@libp2p/interface"; -import { - ConnectionManager, - createDecoder, - createEncoder, - FilterCore, - LightPushCore -} from "@waku/core"; -import { - type Callback, - type ContentTopic, - type CoreProtocolResult, - EConnectionStateEvents, - type IDecodedMessage, - type IDecoder, - type ILightPush, - type IProtoMessage, - type ISubscription, - type Libp2p, - type PeerIdStr, - ProtocolError, - type PubsubTopic, - type SDKProtocolResult, - type SubscribeOptions, - SubscriptionCallback -} from "@waku/interfaces"; -import { WakuMessage } from "@waku/proto"; -import { groupByContentTopic, Logger } from "@waku/utils"; - -import { ReliabilityMonitorManager } from "../../reliability_monitor/index.js"; -import { ReceiverReliabilityMonitor } from "../../reliability_monitor/receiver.js"; -import { PeerManager } from "../peer_manager.js"; - -import { - DEFAULT_KEEP_ALIVE, - DEFAULT_LIGHT_PUSH_FILTER_CHECK, - DEFAULT_LIGHT_PUSH_FILTER_CHECK_INTERVAL, - DEFAULT_SUBSCRIBE_OPTIONS -} from "./constants.js"; - -const log = new Logger("sdk:filter:subscription_manager"); - -export class SubscriptionManager implements ISubscription { - private reliabilityMonitor: ReceiverReliabilityMonitor; - - private keepAliveTimeout: number = DEFAULT_KEEP_ALIVE; - private keepAliveInterval: ReturnType | null = null; - - private enableLightPushFilterCheck = DEFAULT_LIGHT_PUSH_FILTER_CHECK; - - private subscriptionCallbacks: Map< - ContentTopic, - SubscriptionCallback - >; - - public constructor( - private readonly pubsubTopic: PubsubTopic, - private readonly protocol: FilterCore, - private readonly connectionManager: ConnectionManager, - private readonly peerManager: PeerManager, - private readonly libp2p: Libp2p, - private readonly lightPush?: ILightPush - ) { - this.pubsubTopic = pubsubTopic; - this.subscriptionCallbacks = new Map(); - - this.reliabilityMonitor = ReliabilityMonitorManager.createReceiverMonitor( - this.pubsubTopic, - this.peerManager, - () => Array.from(this.subscriptionCallbacks.keys()), - this.protocol.subscribe.bind(this.protocol), - this.sendLightPushCheckMessage.bind(this) - ); - } - - public async subscribe( - decoders: IDecoder | IDecoder[], - callback: Callback, - options: SubscribeOptions = DEFAULT_SUBSCRIBE_OPTIONS - ): Promise { - this.reliabilityMonitor.setMaxPingFailures(options.pingsBeforePeerRenewed); - this.keepAliveTimeout = options.keepAlive || DEFAULT_KEEP_ALIVE; - this.enableLightPushFilterCheck = - options?.enableLightPushFilterCheck || DEFAULT_LIGHT_PUSH_FILTER_CHECK; - - const decodersArray = Array.isArray(decoders) ? decoders : [decoders]; - - // check that all decoders are configured for the same pubsub topic as this subscription - for (const decoder of decodersArray) { - if (decoder.pubsubTopic !== this.pubsubTopic) { - return { - failures: [ - { - error: ProtocolError.TOPIC_DECODER_MISMATCH - } - ], - successes: [] - }; - } - } - - if (this.enableLightPushFilterCheck) { - decodersArray.push( - createDecoder( - this.buildLightPushContentTopic(), - this.pubsubTopic - ) as IDecoder - ); - } - - const decodersGroupedByCT = groupByContentTopic(decodersArray); - const contentTopics = Array.from(decodersGroupedByCT.keys()); - - const peers = await this.peerManager.getPeers(); - const promises = peers.map(async (peer) => - this.subscribeWithPeerVerification(peer, contentTopics) - ); - - const results = await Promise.allSettled(promises); - - const finalResult = this.handleResult(results, "subscribe"); - - // Save the callback functions by content topics so they - // can easily be removed (reciprocally replaced) if `unsubscribe` (reciprocally `subscribe`) - // is called for those content topics - decodersGroupedByCT.forEach((decoders, contentTopic) => { - // Cast the type because a given `subscriptionCallbacks` map may hold - // Decoder that decode to different implementations of `IDecodedMessage` - const subscriptionCallback = { - decoders, - callback - } as unknown as SubscriptionCallback; - - // don't handle case of internal content topic - if (contentTopic === this.buildLightPushContentTopic()) { - return; - } - - // The callback and decoder may override previous values, this is on - // purpose as the user may call `subscribe` to refresh the subscription - this.subscriptionCallbacks.set(contentTopic, subscriptionCallback); - }); - - this.startSubscriptionsMaintenance(this.keepAliveTimeout); - - return finalResult; - } - - public async unsubscribe( - contentTopics: ContentTopic[] - ): Promise { - const peers = await this.peerManager.getPeers(); - const promises = peers.map(async (peer) => { - const response = await this.protocol.unsubscribe( - this.pubsubTopic, - peer, - contentTopics - ); - - contentTopics.forEach((contentTopic: string) => { - this.subscriptionCallbacks.delete(contentTopic); - }); - - return response; - }); - - const results = await Promise.allSettled(promises); - const finalResult = this.handleResult(results, "unsubscribe"); - - if (this.subscriptionCallbacks.size === 0) { - this.stopSubscriptionsMaintenance(); - } - - return finalResult; - } - - public async ping(peerId?: PeerId): Promise { - log.info("Sending keep-alive ping"); - const peers = peerId - ? [peerId] - : (await this.peerManager.getPeers()).map((peer) => peer.id); - - const promises = peers.map((peerId) => this.pingSpecificPeer(peerId)); - const results = await Promise.allSettled(promises); - - return this.handleResult(results, "ping"); - } - - public async unsubscribeAll(): Promise { - const peers = await this.peerManager.getPeers(); - const promises = peers.map(async (peer) => - this.protocol.unsubscribeAll(this.pubsubTopic, peer) - ); - - const results = await Promise.allSettled(promises); - - this.subscriptionCallbacks.clear(); - - const finalResult = this.handleResult(results, "unsubscribeAll"); - - this.stopSubscriptionsMaintenance(); - - return finalResult; - } - - public async processIncomingMessage( - message: WakuMessage, - peerIdStr: PeerIdStr - ): Promise { - const alreadyReceived = this.reliabilityMonitor.notifyMessageReceived( - peerIdStr, - message as IProtoMessage - ); - - if (alreadyReceived) { - log.info("Message already received, skipping"); - return; - } - - const { contentTopic } = message; - const subscriptionCallback = this.subscriptionCallbacks.get(contentTopic); - if (!subscriptionCallback) { - log.error("No subscription callback available for ", contentTopic); - return; - } - log.info( - "Processing message with content topic ", - contentTopic, - " on pubsub topic ", - this.pubsubTopic - ); - await pushMessage(subscriptionCallback, this.pubsubTopic, message); - } - - private async subscribeWithPeerVerification( - peer: Peer, - contentTopics: string[] - ): Promise { - const result = await this.protocol.subscribe( - this.pubsubTopic, - peer, - contentTopics - ); - - await this.sendLightPushCheckMessage(peer); - return result; - } - - private handleResult( - results: PromiseSettledResult[], - type: "ping" | "subscribe" | "unsubscribe" | "unsubscribeAll" - ): SDKProtocolResult { - const result: SDKProtocolResult = { failures: [], successes: [] }; - - for (const promiseResult of results) { - if (promiseResult.status === "rejected") { - log.error( - `Failed to resolve ${type} promise successfully: `, - promiseResult.reason - ); - result.failures.push({ error: ProtocolError.GENERIC_FAIL }); - } else { - const coreResult = promiseResult.value; - if (coreResult.failure) { - result.failures.push(coreResult.failure); - } else { - result.successes.push(coreResult.success); - } - } - } - return result; - } - - private async pingSpecificPeer(peerId: PeerId): Promise { - const peers = await this.peerManager.getPeers(); - const peer = peers.find((p) => p.id.equals(peerId)); - if (!peer) { - return { - success: null, - failure: { - peerId, - error: ProtocolError.NO_PEER_AVAILABLE - } - }; - } - - let result; - try { - result = await this.protocol.ping(peer); - } catch (error) { - result = { - success: null, - failure: { - peerId, - error: ProtocolError.GENERIC_FAIL - } - }; - } - - log.info( - `Received result from filter ping peerId:${peerId.toString()}\tsuccess:${result.success?.toString()}\tfailure:${result.failure?.error}` - ); - await this.reliabilityMonitor.handlePingResult(peerId, result); - return result; - } - - private startSubscriptionsMaintenance(timeout: number): void { - log.info("Starting subscriptions maintenance"); - this.startKeepAlivePings(timeout); - this.startConnectionListener(); - } - - private stopSubscriptionsMaintenance(): void { - log.info("Stopping subscriptions maintenance"); - this.stopKeepAlivePings(); - this.stopConnectionListener(); - } - - private startConnectionListener(): void { - this.connectionManager.addEventListener( - EConnectionStateEvents.CONNECTION_STATUS, - this.connectionListener.bind(this) as (v: CustomEvent) => void - ); - } - - private stopConnectionListener(): void { - this.connectionManager.removeEventListener( - EConnectionStateEvents.CONNECTION_STATUS, - this.connectionListener.bind(this) as (v: CustomEvent) => void - ); - } - - private async connectionListener({ - detail: isConnected - }: CustomEvent): Promise { - if (!isConnected) { - this.stopKeepAlivePings(); - return; - } - - try { - // we do nothing here, as the renewal process is managed internally by `this.ping()` - await this.ping(); - } catch (err) { - log.error(`networkStateListener failed to recover: ${err}`); - } - - this.startKeepAlivePings(this.keepAliveTimeout); - } - - private startKeepAlivePings(timeout: number): void { - if (this.keepAliveInterval) { - log.info("Recurring pings already set up."); - return; - } - - this.keepAliveInterval = setInterval(() => { - void this.ping(); - }, timeout); - } - - private stopKeepAlivePings(): void { - if (!this.keepAliveInterval) { - log.info("Already stopped recurring pings."); - return; - } - - log.info("Stopping recurring pings."); - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - - private async sendLightPushCheckMessage(peer: Peer): Promise { - if ( - this.lightPush && - this.libp2p && - this.reliabilityMonitor.shouldVerifyPeer(peer.id) - ) { - const encoder = createEncoder({ - contentTopic: this.buildLightPushContentTopic(), - pubsubTopic: this.pubsubTopic, - ephemeral: true - }); - - const message = { payload: new Uint8Array(1) }; - const protoMessage = await encoder.toProtoObj(message); - - // make a delay to be sure message is send when subscription is in place - setTimeout( - (async () => { - const result = await (this.lightPush!.protocol as LightPushCore).send( - encoder, - message, - peer - ); - this.reliabilityMonitor.notifyMessageSent(peer.id, protoMessage); - if (result.failure) { - log.error( - `failed to send lightPush ping message to peer:${peer.id.toString()}\t${result.failure.error}` - ); - return; - } - }) as () => void, - DEFAULT_LIGHT_PUSH_FILTER_CHECK_INTERVAL - ); - } - } - - private buildLightPushContentTopic(): string { - return `/js-waku-subscription-ping/1/${this.libp2p.peerId.toString()}/utf8`; - } -} - -async function pushMessage( - subscriptionCallback: SubscriptionCallback, - pubsubTopic: PubsubTopic, - message: WakuMessage -): Promise { - const { decoders, callback } = subscriptionCallback; - - const { contentTopic } = message; - if (!contentTopic) { - log.warn("Message has no content topic, skipping"); - return; - } - - try { - const decodePromises = decoders.map((dec) => - dec - .fromProtoObj(pubsubTopic, message as IProtoMessage) - .then((decoded) => decoded || Promise.reject("Decoding failed")) - ); - - const decodedMessage = await Promise.any(decodePromises); - - await callback(decodedMessage); - } catch (e) { - log.error("Error decoding message", e); - } -} diff --git a/packages/sdk/src/protocols/filter/subscription_monitor.ts b/packages/sdk/src/protocols/filter/subscription_monitor.ts new file mode 100644 index 0000000000..966e197f89 --- /dev/null +++ b/packages/sdk/src/protocols/filter/subscription_monitor.ts @@ -0,0 +1,287 @@ +import type { EventHandler, Peer, PeerId } from "@libp2p/interface"; +import { FilterCore } from "@waku/core"; +import type { + FilterProtocolOptions, + IConnectionManager, + ILightPush, + IProtoMessage, + Libp2p +} from "@waku/interfaces"; +import { EConnectionStateEvents } from "@waku/interfaces"; +import { messageHashStr } from "@waku/message-hash"; + +import { PeerManager } from "../peer_manager.js"; + +// TODO(weboko): consider adding as config property or combine with maxAllowedPings +const MAX_SUBSCRIBE_ATTEMPTS = 3; + +type SubscriptionMonitorConstructorOptions = { + pubsubTopic: string; + config: FilterProtocolOptions; + libp2p: Libp2p; + connectionManager: IConnectionManager; + filter: FilterCore; + peerManager: PeerManager; + lightPush?: ILightPush; + activeSubscriptions: Map; +}; + +export class SubscriptionMonitor { + /** + * Cached peers that are in use by subscription. + * Needed to understand if they disconnect later or not. + */ + public peers: Peer[] = []; + + private isStarted: boolean = false; + + private readonly pubsubTopic: string; + private readonly config: FilterProtocolOptions; + + private readonly libp2p: Libp2p; + private readonly filter: FilterCore; + private readonly peerManager: PeerManager; + private readonly connectionManager: IConnectionManager; + private readonly activeSubscriptions: Map; + + private keepAliveIntervalId: number | undefined; + private pingFailedAttempts = new Map(); + + private receivedMessagesFormPeer = new Set(); + private receivedMessages = new Set(); + private verifiedPeers = new Set(); + + public constructor(options: SubscriptionMonitorConstructorOptions) { + this.config = options.config; + this.connectionManager = options.connectionManager; + this.filter = options.filter; + this.peerManager = options.peerManager; + this.libp2p = options.libp2p; + this.activeSubscriptions = options.activeSubscriptions; + this.pubsubTopic = options.pubsubTopic; + + this.onConnectionChange = this.onConnectionChange.bind(this); + this.onPeerConnected = this.onPeerConnected.bind(this); + this.onPeerDisconnected = this.onPeerDisconnected.bind(this); + } + + /** + * @returns content topic used for Filter verification + */ + public get reservedContentTopic(): string { + return `/js-waku-subscription-ping/1/${this.libp2p.peerId.toString()}/utf8`; + } + + /** + * Starts: + * - recurring ping queries; + * - connection event observers; + */ + public start(): void { + if (this.isStarted) { + return; + } + + this.isStarted = true; + + this.startKeepAlive(); + this.startConnectionListener(); + this.startPeerConnectionListener(); + } + + /** + * Stops all recurring queries, event listeners or timers. + */ + public stop(): void { + if (!this.isStarted) { + return; + } + + this.isStarted = false; + + this.stopKeepAlive(); + this.stopConnectionListener(); + this.stopPeerConnectionListener(); + } + + /** + * Method to get peers that are used by particular subscription or, if initially called, peers that can be used by subscription. + * @returns array of peers + */ + public async getPeers(): Promise { + if (!this.isStarted) { + this.peers = await this.peerManager.getPeers(); + } + + return this.peers; + } + + /** + * Notifies monitor if message was received. + * + * @param peerId peer from which message is received + * @param message received message + * + * @returns true if message was received from peer + */ + public notifyMessageReceived( + peerId: string, + message: IProtoMessage + ): boolean { + const hash = this.buildMessageHash(message); + + this.verifiedPeers.add(peerId); + this.receivedMessagesFormPeer.add(`${peerId}-${hash}`); + + if (this.receivedMessages.has(hash)) { + return true; + } + + this.receivedMessages.add(hash); + + return false; + } + + private buildMessageHash(message: IProtoMessage): string { + return messageHashStr(this.pubsubTopic, message); + } + + private startConnectionListener(): void { + this.connectionManager.addEventListener( + EConnectionStateEvents.CONNECTION_STATUS, + this.onConnectionChange as (v: CustomEvent) => void + ); + } + + private stopConnectionListener(): void { + this.connectionManager.removeEventListener( + EConnectionStateEvents.CONNECTION_STATUS, + this.onConnectionChange as (v: CustomEvent) => void + ); + } + + private async onConnectionChange({ + detail: isConnected + }: CustomEvent): Promise { + if (!isConnected) { + this.stopKeepAlive(); + return; + } + + await Promise.all(this.peers.map((peer) => this.ping(peer, true))); + this.startKeepAlive(); + } + + private startKeepAlive(): void { + if (this.keepAliveIntervalId) { + return; + } + + this.keepAliveIntervalId = setInterval(() => { + void this.peers.map((peer) => this.ping(peer)); + }, this.config.keepAliveIntervalMs) as unknown as number; + } + + private stopKeepAlive(): void { + if (!this.keepAliveIntervalId) { + return; + } + + clearInterval(this.keepAliveIntervalId); + this.keepAliveIntervalId = undefined; + } + + private startPeerConnectionListener(): void { + this.libp2p.addEventListener( + "peer:connect", + this.onPeerConnected as EventHandler> + ); + this.libp2p.addEventListener( + "peer:disconnect", + this.onPeerDisconnected as EventHandler> + ); + } + + private stopPeerConnectionListener(): void { + this.libp2p.removeEventListener( + "peer:connect", + this.onPeerConnected as EventHandler> + ); + this.libp2p.removeEventListener( + "peer:disconnect", + this.onPeerDisconnected as EventHandler> + ); + } + + private async onPeerConnected(_event: CustomEvent): Promise { + // TODO(weboko): use config.numOfUsedPeers here + if (this.peers.length > 0) { + return; + } + + this.peers = await this.peerManager.getPeers(); + await Promise.all(this.peers.map((peer) => this.subscribe(peer))); + } + + private async onPeerDisconnected(event: CustomEvent): Promise { + const hasNotBeenUsed = !this.peers.find((p) => p.id.equals(event.detail)); + if (hasNotBeenUsed) { + return; + } + + this.peers = await this.peerManager.getPeers(); + await Promise.all(this.peers.map((peer) => this.subscribe(peer))); + } + + private async subscribe(_peer: Peer | undefined): Promise { + let peer: Peer | undefined = _peer; + + for (let i = 0; i < MAX_SUBSCRIBE_ATTEMPTS; i++) { + if (!peer) { + return; + } + + const response = await this.filter.subscribe( + this.pubsubTopic, + peer, + Array.from(this.activeSubscriptions.keys()) + ); + + if (response.success) { + return; + } + + peer = await this.peerManager.requestRenew(peer.id); + } + } + + private async ping( + peer: Peer, + renewOnFirstFail: boolean = false + ): Promise { + const peerIdStr = peer.id.toString(); + const response = await this.filter.ping(peer); + + if (response.failure && renewOnFirstFail) { + const newPeer = await this.peerManager.requestRenew(peer.id); + await this.subscribe(newPeer); + return; + } + + if (response.failure) { + const prev = this.pingFailedAttempts.get(peerIdStr) || 0; + this.pingFailedAttempts.set(peerIdStr, prev + 1); + } + + if (response.success) { + this.pingFailedAttempts.set(peerIdStr, 0); + } + + const madeAttempts = this.pingFailedAttempts.get(peerIdStr) || 0; + + if (madeAttempts >= this.config.pingsBeforePeerRenewed) { + const newPeer = await this.peerManager.requestRenew(peer.id); + await this.subscribe(newPeer); + } + } +} diff --git a/packages/sdk/src/protocols/filter/utils.ts b/packages/sdk/src/protocols/filter/utils.ts new file mode 100644 index 0000000000..9c926ae36c --- /dev/null +++ b/packages/sdk/src/protocols/filter/utils.ts @@ -0,0 +1,15 @@ +import { FilterProtocolOptions } from "@waku/interfaces"; + +import * as C from "./constants.js"; + +export const buildConfig = ( + config?: Partial +): FilterProtocolOptions => { + return { + keepAliveIntervalMs: config?.keepAliveIntervalMs || C.DEFAULT_KEEP_ALIVE, + pingsBeforePeerRenewed: + config?.pingsBeforePeerRenewed || C.DEFAULT_MAX_PINGS, + enableLightPushFilterCheck: + config?.enableLightPushFilterCheck || C.DEFAULT_LIGHT_PUSH_FILTER_CHECK + }; +}; diff --git a/packages/sdk/src/protocols/peer_manager.ts b/packages/sdk/src/protocols/peer_manager.ts index c4d26d9ae4..4b874ce89c 100644 --- a/packages/sdk/src/protocols/peer_manager.ts +++ b/packages/sdk/src/protocols/peer_manager.ts @@ -22,6 +22,9 @@ export class PeerManager { private readonly libp2p: Libp2p; public constructor(params: PeerManagerParams) { + this.onConnected = this.onConnected.bind(this); + this.onDisconnected = this.onDisconnected.bind(this); + this.numPeersToUse = params?.config?.numPeersToUse || DEFAULT_NUM_PEERS_TO_USE; @@ -58,7 +61,20 @@ export class PeerManager { .map((c) => this.mapConnectionToPeer(c)) ); - return result[0]; + const newPeer = result[0]; + + if (!newPeer) { + log.warn( + `requestRenew: Couldn't renew peer ${peerId.toString()} - no peers.` + ); + return; + } + + log.info( + `requestRenew: Renewed peer ${peerId.toString()} to ${newPeer.id.toString()}` + ); + + return newPeer; } private startConnectionListener(): void { @@ -107,7 +123,9 @@ export class PeerManager { } private lockConnection(c: Connection): Connection { - log.info(`Locking connection for peerId=${c.remotePeer.toString()}`); + log.info( + `requestRenew: Locking connection for peerId=${c.remotePeer.toString()}` + ); c.tags.push(CONNECTION_LOCK_TAG); return c; } diff --git a/packages/sdk/src/reliability_monitor/index.ts b/packages/sdk/src/reliability_monitor/index.ts deleted file mode 100644 index 120d208404..0000000000 --- a/packages/sdk/src/reliability_monitor/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Peer } from "@libp2p/interface"; -import { - ContentTopic, - CoreProtocolResult, - PubsubTopic -} from "@waku/interfaces"; - -import { PeerManager } from "../protocols/peer_manager.js"; - -import { ReceiverReliabilityMonitor } from "./receiver.js"; - -export class ReliabilityMonitorManager { - private static receiverMonitors: Map< - PubsubTopic, - ReceiverReliabilityMonitor - > = new Map(); - - public static createReceiverMonitor( - pubsubTopic: PubsubTopic, - peerManager: PeerManager, - getContentTopics: () => ContentTopic[], - protocolSubscribe: ( - pubsubTopic: PubsubTopic, - peer: Peer, - contentTopics: ContentTopic[] - ) => Promise, - sendLightPushMessage: (peer: Peer) => Promise - ): ReceiverReliabilityMonitor { - if (ReliabilityMonitorManager.receiverMonitors.has(pubsubTopic)) { - return ReliabilityMonitorManager.receiverMonitors.get(pubsubTopic)!; - } - - const monitor = new ReceiverReliabilityMonitor( - pubsubTopic, - peerManager, - getContentTopics, - protocolSubscribe, - sendLightPushMessage - ); - ReliabilityMonitorManager.receiverMonitors.set(pubsubTopic, monitor); - return monitor; - } - - private constructor() {} - - public static stop(pubsubTopic: PubsubTopic): void { - this.receiverMonitors.delete(pubsubTopic); - } - - public static stopAll(): void { - for (const [pubsubTopic, monitor] of this.receiverMonitors) { - monitor.setMaxPingFailures(undefined); - this.receiverMonitors.delete(pubsubTopic); - } - } -} diff --git a/packages/sdk/src/reliability_monitor/receiver.ts b/packages/sdk/src/reliability_monitor/receiver.ts deleted file mode 100644 index cbde6ddd3c..0000000000 --- a/packages/sdk/src/reliability_monitor/receiver.ts +++ /dev/null @@ -1,185 +0,0 @@ -import type { Peer, PeerId } from "@libp2p/interface"; -import { - ContentTopic, - CoreProtocolResult, - IProtoMessage, - PeerIdStr, - PubsubTopic -} from "@waku/interfaces"; -import { messageHashStr } from "@waku/message-hash"; -import { Logger } from "@waku/utils"; -import { bytesToUtf8 } from "@waku/utils/bytes"; - -import { PeerManager } from "../protocols/peer_manager.js"; - -const log = new Logger("sdk:receiver:reliability_monitor"); - -const DEFAULT_MAX_PINGS = 3; -const MESSAGE_VERIFICATION_DELAY = 5_000; - -export class ReceiverReliabilityMonitor { - private receivedMessagesFormPeer = new Set(); - private receivedMessages = new Set(); - private scheduledVerification = new Map(); - private verifiedPeers = new Set(); - - private peerFailures: Map = new Map(); - private maxPingFailures: number = DEFAULT_MAX_PINGS; - private peerRenewalLocks: Set = new Set(); - - public constructor( - private readonly pubsubTopic: PubsubTopic, - private readonly peerManager: PeerManager, - private getContentTopics: () => ContentTopic[], - private protocolSubscribe: ( - pubsubTopic: PubsubTopic, - peer: Peer, - contentTopics: ContentTopic[] - ) => Promise, - private sendLightPushMessage: (peer: Peer) => Promise - ) {} - - public setMaxPingFailures(value: number | undefined): void { - if (value === undefined) { - return; - } - this.maxPingFailures = value; - } - - public async handlePingResult( - peerId: PeerId, - result?: CoreProtocolResult - ): Promise { - if (result?.success) { - this.peerFailures.delete(peerId.toString()); - return; - } - - const failures = (this.peerFailures.get(peerId.toString()) || 0) + 1; - this.peerFailures.set(peerId.toString(), failures); - - if (failures >= this.maxPingFailures) { - try { - log.info( - `Attempting to renew ${peerId.toString()} due to ping failures.` - ); - await this.renewAndSubscribePeer(peerId); - this.peerFailures.delete(peerId.toString()); - } catch (error) { - log.error(`Failed to renew peer ${peerId.toString()}: ${error}.`); - } - } - } - - public notifyMessageReceived( - peerIdStr: string, - message: IProtoMessage - ): boolean { - const hash = this.buildMessageHash(message); - - this.verifiedPeers.add(peerIdStr); - this.receivedMessagesFormPeer.add(`${peerIdStr}-${hash}`); - - log.info( - `notifyMessage received debug: ephemeral:${message.ephemeral}\t${bytesToUtf8(message.payload)}` - ); - log.info(`notifyMessage received: peer:${peerIdStr}\tmessage:${hash}`); - - if (this.receivedMessages.has(hash)) { - return true; - } - - this.receivedMessages.add(hash); - - return false; - } - - public notifyMessageSent(peerId: PeerId, message: IProtoMessage): void { - const peerIdStr = peerId.toString(); - const hash = this.buildMessageHash(message); - - log.info(`notifyMessage sent debug: ${bytesToUtf8(message.payload)}`); - - if (this.scheduledVerification.has(peerIdStr)) { - log.warn( - `notifyMessage sent: attempting to schedule verification for pending peer:${peerIdStr}\tmessage:${hash}` - ); - return; - } - - const timeout = setTimeout( - (async () => { - const receivedAnyMessage = this.verifiedPeers.has(peerIdStr); - const receivedTestMessage = this.receivedMessagesFormPeer.has( - `${peerIdStr}-${hash}` - ); - - if (receivedAnyMessage || receivedTestMessage) { - log.info( - `notifyMessage sent setTimeout: verified that peer pushes filter messages, peer:${peerIdStr}\tmessage:${hash}` - ); - return; - } - - log.warn( - `notifyMessage sent setTimeout: peer didn't return probe message, attempting renewAndSubscribe, peer:${peerIdStr}\tmessage:${hash}` - ); - this.scheduledVerification.delete(peerIdStr); - await this.renewAndSubscribePeer(peerId); - }) as () => void, - MESSAGE_VERIFICATION_DELAY - ) as unknown as number; - - this.scheduledVerification.set(peerIdStr, timeout); - } - - public shouldVerifyPeer(peerId: PeerId): boolean { - const peerIdStr = peerId.toString(); - - const isPeerVerified = this.verifiedPeers.has(peerIdStr); - const isVerificationPending = this.scheduledVerification.has(peerIdStr); - - return !(isPeerVerified || isVerificationPending); - } - - private buildMessageHash(message: IProtoMessage): string { - return messageHashStr(this.pubsubTopic, message); - } - - private async renewAndSubscribePeer( - peerId: PeerId - ): Promise { - const peerIdStr = peerId.toString(); - try { - if (this.peerRenewalLocks.has(peerIdStr)) { - log.info(`Peer ${peerIdStr} is already being renewed.`); - return; - } - - this.peerRenewalLocks.add(peerIdStr); - - const newPeer = await this.peerManager.requestRenew(peerId); - if (!newPeer) { - log.warn(`Failed to renew peer ${peerIdStr}: No new peer found.`); - return; - } - - await this.protocolSubscribe( - this.pubsubTopic, - newPeer, - this.getContentTopics() - ); - - await this.sendLightPushMessage(newPeer); - - this.peerFailures.delete(peerIdStr); - - return newPeer; - } catch (error) { - log.error(`Failed to renew peer ${peerIdStr}: ${error}.`); - return; - } finally { - this.peerRenewalLocks.delete(peerIdStr); - } - } -} diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index bea192c202..97ab276ed8 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -3,6 +3,7 @@ import type { Peer, PeerId, Stream } from "@libp2p/interface"; import { multiaddr, Multiaddr, MultiaddrInput } from "@multiformats/multiaddr"; import { ConnectionManager, getHealthManager, StoreCodec } from "@waku/core"; import type { + CreateNodeOptions, IFilter, IHealthManager, ILightPush, @@ -10,7 +11,6 @@ import type { IStore, IWaku, Libp2p, - ProtocolCreateOptions, PubsubTopic } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; @@ -20,26 +20,11 @@ import { wakuFilter } from "../protocols/filter/index.js"; import { wakuLightPush } from "../protocols/light_push/index.js"; import { PeerManager } from "../protocols/peer_manager.js"; import { wakuStore } from "../protocols/store/index.js"; -import { ReliabilityMonitorManager } from "../reliability_monitor/index.js"; import { waitForRemotePeer } from "./wait_for_remote_peer.js"; -export const DefaultUserAgent = "js-waku"; -export const DefaultPingMaxInboundStreams = 10; - const log = new Logger("waku"); -export interface WakuOptions { - /** - * Set the user agent string to be used in identification of the node. - * @default {@link @waku/core.DefaultUserAgent} - */ - userAgent?: string; -} - -export type CreateWakuNodeOptions = ProtocolCreateOptions & - Partial; - type ProtocolsEnabled = { filter?: boolean; lightpush?: boolean; @@ -59,7 +44,7 @@ export class WakuNode implements IWaku { public constructor( public readonly pubsubTopics: PubsubTopic[], - options: CreateWakuNodeOptions, + options: CreateNodeOptions, libp2p: Libp2p, protocolsEnabled: ProtocolsEnabled, relay?: IRelay @@ -116,7 +101,8 @@ export class WakuNode implements IWaku { const filter = wakuFilter( this.connectionManager, this.peerManager, - this.lightPush + this.lightPush, + options.filter ); this.filter = filter(libp2p); } @@ -200,7 +186,6 @@ export class WakuNode implements IWaku { } public async stop(): Promise { - ReliabilityMonitorManager.stopAll(); this.peerManager.stop(); this.connectionManager.stop(); await this.libp2p.stop(); diff --git a/packages/tests/src/lib/runNodes.ts b/packages/tests/src/lib/runNodes.ts index adb37170f8..b7d02d0c13 100644 --- a/packages/tests/src/lib/runNodes.ts +++ b/packages/tests/src/lib/runNodes.ts @@ -1,8 +1,4 @@ -import { - NetworkConfig, - ProtocolCreateOptions, - Protocols -} from "@waku/interfaces"; +import { CreateNodeOptions, NetworkConfig, Protocols } from "@waku/interfaces"; import { createRelayNode } from "@waku/relay"; import { createLightNode, WakuNode } from "@waku/sdk"; import { @@ -46,7 +42,7 @@ export async function runNodes( }, { retries: 3 } ); - const waku_options: ProtocolCreateOptions = { + const waku_options: CreateNodeOptions = { staticNoiseKey: NOISE_KEY_1, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, networkConfig: shardInfo diff --git a/packages/tests/src/utils/nodes.ts b/packages/tests/src/utils/nodes.ts index 920bb392ba..c3e8cc7a9b 100644 --- a/packages/tests/src/utils/nodes.ts +++ b/packages/tests/src/utils/nodes.ts @@ -1,9 +1,9 @@ import { + CreateNodeOptions, DefaultNetworkConfig, IWaku, LightNode, NetworkConfig, - ProtocolCreateOptions, Protocols } from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; @@ -35,7 +35,7 @@ export async function runMultipleNodes( withoutFilter ); - const wakuOptions: ProtocolCreateOptions = { + const wakuOptions: CreateNodeOptions = { staticNoiseKey: NOISE_KEY_1, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } diff --git a/packages/tests/tests/filter/utils.ts b/packages/tests/tests/filter/utils.ts index 649c302cf0..938ac16d99 100644 --- a/packages/tests/tests/filter/utils.ts +++ b/packages/tests/tests/filter/utils.ts @@ -1,11 +1,11 @@ import { createDecoder, createEncoder } from "@waku/core"; import { + CreateNodeOptions, DefaultNetworkConfig, ISubscription, IWaku, LightNode, NetworkConfig, - ProtocolCreateOptions, Protocols } from "@waku/interfaces"; import { createLightNode } from "@waku/sdk"; @@ -85,7 +85,7 @@ export async function runMultipleNodes( withoutFilter ); - const wakuOptions: ProtocolCreateOptions = { + const wakuOptions: CreateNodeOptions = { staticNoiseKey: NOISE_KEY_1, libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } diff --git a/packages/tests/tests/wait_for_remote_peer.node.spec.ts b/packages/tests/tests/wait_for_remote_peer.node.spec.ts index 37b5d6e632..a324b15459 100644 --- a/packages/tests/tests/wait_for_remote_peer.node.spec.ts +++ b/packages/tests/tests/wait_for_remote_peer.node.spec.ts @@ -115,7 +115,9 @@ describe("Wait for remote peer", function () { await delay(1000); await waku2.waitForPeers([Protocols.Store]); - const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); + const peers = (await waku2.getConnectedPeers()).map((peer) => + peer.id.toString() + ); const nimPeerId = multiAddrWithId.getPeerId(); expect(nimPeerId).to.not.be.undefined; @@ -143,7 +145,9 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waitPromise; - const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); + const peers = (await waku2.getConnectedPeers()).map((peer) => + peer.id.toString() + ); const nimPeerId = multiAddrWithId.getPeerId(); @@ -170,7 +174,9 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waku2.waitForPeers([Protocols.LightPush]); - const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); + const peers = (await waku2.getConnectedPeers()).map((peer) => + peer.id.toString() + ); const nimPeerId = multiAddrWithId.getPeerId(); @@ -197,7 +203,9 @@ describe("Wait for remote peer", function () { await waku2.dial(multiAddrWithId); await waku2.waitForPeers([Protocols.Filter]); - const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); + const peers = (await waku2.getConnectedPeers()).map((peer) => + peer.id.toString() + ); const nimPeerId = multiAddrWithId.getPeerId(); @@ -229,7 +237,9 @@ describe("Wait for remote peer", function () { Protocols.LightPush ]); - const peers = (await waku2.getPeers()).map((peer) => peer.id.toString()); + const peers = (await waku2.getConnectedPeers()).map((peer) => + peer.id.toString() + ); const nimPeerId = multiAddrWithId.getPeerId(); diff --git a/packages/tests/tests/waku.node.spec.ts b/packages/tests/tests/waku.node.spec.ts index 0d81d7c8bc..8d037d1bd2 100644 --- a/packages/tests/tests/waku.node.spec.ts +++ b/packages/tests/tests/waku.node.spec.ts @@ -11,8 +11,7 @@ import { import { createRelayNode } from "@waku/relay"; import { createLightNode, - createEncoder as createPlainEncoder, - DefaultUserAgent + createEncoder as createPlainEncoder } from "@waku/sdk"; import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; @@ -278,7 +277,7 @@ describe("User Agent", function () { waku1UserAgent ); expect(bytesToUtf8(waku2PeerInfo.metadata.get("AgentVersion")!)).to.eq( - DefaultUserAgent + "js-waku" ); }); }); diff --git a/packages/utils/src/common/sharding/type_guards.ts b/packages/utils/src/common/sharding/type_guards.ts index b0433e11cc..9ab53373aa 100644 --- a/packages/utils/src/common/sharding/type_guards.ts +++ b/packages/utils/src/common/sharding/type_guards.ts @@ -1,11 +1,11 @@ import type { ContentTopicInfo, - ProtocolCreateOptions, + CreateNodeOptions, StaticSharding } from "@waku/interfaces"; export function isStaticSharding( - config: NonNullable + config: NonNullable ): config is StaticSharding { return ( "clusterId" in config && "shards" in config && !("contentTopics" in config) @@ -13,7 +13,7 @@ export function isStaticSharding( } export function isAutoSharding( - config: NonNullable + config: NonNullable ): config is ContentTopicInfo { return "contentTopics" in config; } From ebb00f41e5a52153044b6b66dcab4416023678dd Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 29 Jan 2025 22:52:10 +0100 Subject: [PATCH 15/22] up lock --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 36d314ccb7..a904f9d19c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40953,7 +40953,7 @@ "name": "@waku/browser-tests", "version": "0.1.0", "devDependencies": { - "@playwright/test": "^1.48.1", + "@playwright/test": "^1.50.0", "@waku/create-app": "^0.1.1-504bcd4", "dotenv-flow": "^4.1.0", "serve": "^14.2.3" From 93eebfa36247e42ddfeba4a47709d57f5ddb5fd6 Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 21:16:07 +0100 Subject: [PATCH 16/22] make peer retrieval probabilistic --- packages/sdk/src/protocols/store/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index 5a95d8952b..909e830c93 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -241,7 +241,8 @@ export class Store implements IStore { const peers = await this.peerManager.getPeers(); if (peers.length > 0) { - return peers[0]; + // TODO(weboko): implement smart way of getting a peer https://github.com/waku-org/js-waku/issues/2243 + return peers[Math.floor(Math.random() * peers.length)]; } log.error("No peers available to use."); From 070b5a4fa0abdd9d09479af63b253bb2fcb6e98a Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 21:36:18 +0100 Subject: [PATCH 17/22] add comments --- .../sdk/src/protocols/filter/subscription_monitor.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/protocols/filter/subscription_monitor.ts b/packages/sdk/src/protocols/filter/subscription_monitor.ts index 966e197f89..e10751f876 100644 --- a/packages/sdk/src/protocols/filter/subscription_monitor.ts +++ b/packages/sdk/src/protocols/filter/subscription_monitor.ts @@ -213,9 +213,11 @@ export class SubscriptionMonitor { ); } + // this method keeps track of new connections and will trigger subscribe request if needed private async onPeerConnected(_event: CustomEvent): Promise { - // TODO(weboko): use config.numOfUsedPeers here - if (this.peers.length > 0) { + // TODO(weboko): use config.numOfUsedPeers instead of this.peers + const hasSomePeers = this.peers.length > 0; + if (hasSomePeers) { return; } @@ -223,6 +225,7 @@ export class SubscriptionMonitor { await Promise.all(this.peers.map((peer) => this.subscribe(peer))); } + // this method keeps track of disconnects and will trigger subscribe request if needed private async onPeerDisconnected(event: CustomEvent): Promise { const hasNotBeenUsed = !this.peers.find((p) => p.id.equals(event.detail)); if (hasNotBeenUsed) { @@ -230,6 +233,9 @@ export class SubscriptionMonitor { } this.peers = await this.peerManager.getPeers(); + + // we trigger subscribe for peer that was used before + // it will expectedly fail and we will initiate addition of a new peer await Promise.all(this.peers.map((peer) => this.subscribe(peer))); } From 38067b378f6f05af8d5bced248baebbe27ffcecb Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 23:10:42 +0100 Subject: [PATCH 18/22] up lightpush tests --- .../src/protocols/light_push/light_push.spec.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/sdk/src/protocols/light_push/light_push.spec.ts b/packages/sdk/src/protocols/light_push/light_push.spec.ts index 7c695212b5..305a194941 100644 --- a/packages/sdk/src/protocols/light_push/light_push.spec.ts +++ b/packages/sdk/src/protocols/light_push/light_push.spec.ts @@ -57,8 +57,7 @@ describe("LightPush SDK", () => { peers: [mockPeer("1"), mockPeer("2"), mockPeer("3"), mockPeer("4")] }); - // check default value that should be 2 - lightPush = mockLightPush({ libp2p }); + lightPush = mockLightPush({ libp2p, numPeersToUse: 2 }); let sendSpy = sinon.spy( (_encoder: any, _message: any, peer: Peer) => ({ success: peer.id }) as any @@ -157,9 +156,15 @@ type MockLightPushOptions = { function mockLightPush(options: MockLightPushOptions): LightPush { return new LightPush( { - configuredPubsubTopics: options.pubsubTopics || [PUBSUB_TOPIC] - } as unknown as ConnectionManager, - {} as unknown as PeerManager, + pubsubTopics: options.pubsubTopics || [PUBSUB_TOPIC] + } as ConnectionManager, + { + getPeers: () => + options.libp2p + .getPeers() + .map((id) => mockPeer(id.toString())) + .slice(0, options.numPeersToUse || options.libp2p.getPeers().length) + } as unknown as PeerManager, options.libp2p ); } From fb9556a539dfa2fdb9d943c2a37e2e5b4740016c Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 23:20:50 +0100 Subject: [PATCH 19/22] add tests for peer_manager, improve folder structure --- packages/sdk/src/protocols/filter/index.ts | 2 +- .../sdk/src/protocols/filter/subscription.ts | 2 +- .../protocols/filter/subscription_monitor.ts | 2 +- .../protocols/light_push/light_push.spec.ts | 2 +- .../src/protocols/light_push/light_push.ts | 2 +- .../sdk/src/protocols/peer_manager/index.ts | 1 + .../peer_manager/peer_manager.spec.ts | 109 ++++++++++++++++++ .../{ => peer_manager}/peer_manager.ts | 0 packages/sdk/src/protocols/store/index.ts | 2 +- packages/sdk/src/waku/waku.ts | 2 +- 10 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 packages/sdk/src/protocols/peer_manager/index.ts create mode 100644 packages/sdk/src/protocols/peer_manager/peer_manager.spec.ts rename packages/sdk/src/protocols/{ => peer_manager}/peer_manager.ts (100%) diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index 1e4b6d86bf..1f7c513b7f 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -22,7 +22,7 @@ import { toAsyncIterator } from "@waku/utils"; -import { PeerManager } from "../peer_manager.js"; +import { PeerManager } from "../peer_manager/index.js"; import { Subscription } from "./subscription.js"; import { buildConfig } from "./utils.js"; diff --git a/packages/sdk/src/protocols/filter/subscription.ts b/packages/sdk/src/protocols/filter/subscription.ts index c585a05408..39e2a2ed12 100644 --- a/packages/sdk/src/protocols/filter/subscription.ts +++ b/packages/sdk/src/protocols/filter/subscription.ts @@ -19,7 +19,7 @@ import { import { WakuMessage } from "@waku/proto"; import { groupByContentTopic, Logger } from "@waku/utils"; -import { PeerManager } from "../peer_manager.js"; +import { PeerManager } from "../peer_manager/index.js"; import { SubscriptionMonitor } from "./subscription_monitor.js"; diff --git a/packages/sdk/src/protocols/filter/subscription_monitor.ts b/packages/sdk/src/protocols/filter/subscription_monitor.ts index e10751f876..3cf65088d1 100644 --- a/packages/sdk/src/protocols/filter/subscription_monitor.ts +++ b/packages/sdk/src/protocols/filter/subscription_monitor.ts @@ -10,7 +10,7 @@ import type { import { EConnectionStateEvents } from "@waku/interfaces"; import { messageHashStr } from "@waku/message-hash"; -import { PeerManager } from "../peer_manager.js"; +import { PeerManager } from "../peer_manager/index.js"; // TODO(weboko): consider adding as config property or combine with maxAllowedPings const MAX_SUBSCRIBE_ATTEMPTS = 3; diff --git a/packages/sdk/src/protocols/light_push/light_push.spec.ts b/packages/sdk/src/protocols/light_push/light_push.spec.ts index 305a194941..a461c89828 100644 --- a/packages/sdk/src/protocols/light_push/light_push.spec.ts +++ b/packages/sdk/src/protocols/light_push/light_push.spec.ts @@ -10,7 +10,7 @@ import { utf8ToBytes } from "@waku/utils/bytes"; import { expect } from "chai"; import sinon from "sinon"; -import { PeerManager } from "../peer_manager.js"; +import { PeerManager } from "../peer_manager/index.js"; import { LightPush } from "./light_push.js"; diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/protocols/light_push/light_push.ts index f04f3175b6..0c71cc8012 100644 --- a/packages/sdk/src/protocols/light_push/light_push.ts +++ b/packages/sdk/src/protocols/light_push/light_push.ts @@ -13,7 +13,7 @@ import { } from "@waku/interfaces"; import { ensurePubsubTopicIsConfigured, Logger } from "@waku/utils"; -import { PeerManager } from "../peer_manager.js"; +import { PeerManager } from "../peer_manager/index.js"; const log = new Logger("sdk:light-push"); diff --git a/packages/sdk/src/protocols/peer_manager/index.ts b/packages/sdk/src/protocols/peer_manager/index.ts new file mode 100644 index 0000000000..73397f7a45 --- /dev/null +++ b/packages/sdk/src/protocols/peer_manager/index.ts @@ -0,0 +1 @@ +export { PeerManager } from "./peer_manager.js"; diff --git a/packages/sdk/src/protocols/peer_manager/peer_manager.spec.ts b/packages/sdk/src/protocols/peer_manager/peer_manager.spec.ts new file mode 100644 index 0000000000..358f55489e --- /dev/null +++ b/packages/sdk/src/protocols/peer_manager/peer_manager.spec.ts @@ -0,0 +1,109 @@ +import { Connection, Peer, PeerId } from "@libp2p/interface"; +import { Libp2p } from "@waku/interfaces"; +import { expect } from "chai"; +import sinon from "sinon"; + +import { PeerManager } from "./peer_manager.js"; + +describe.only("PeerManager", () => { + let libp2p: Libp2p; + let peerManager: PeerManager; + + beforeEach(() => { + libp2p = mockLibp2p(); + peerManager = new PeerManager({ libp2p }); + }); + + afterEach(() => { + peerManager.stop(); + sinon.restore(); + }); + + it("should initialize with default number of peers", () => { + expect(peerManager["numPeersToUse"]).to.equal(2); + }); + + it("should initialize with custom number of peers", () => { + peerManager = new PeerManager({ libp2p, config: { numPeersToUse: 3 } }); + expect(peerManager["numPeersToUse"]).to.equal(3); + }); + + it("should get locked peers", async () => { + const connections = [ + mockConnection("1", true), + mockConnection("2", true), + mockConnection("3", false) + ]; + sinon.stub(libp2p, "getConnections").returns(connections); + + const peers = await peerManager.getPeers(); + expect(peers.length).to.equal(2); + }); + + it("should request renew when peer disconnects", async () => { + const connections = [ + mockConnection("1", true), + mockConnection("2", false), + mockConnection("3", false) + ]; + sinon.stub(libp2p, "getConnections").returns(connections); + + const peer = await peerManager.requestRenew("1"); + expect(peer).to.not.be.undefined; + expect(peer?.id).to.not.equal("1"); + }); + + it("should handle connection events", () => { + const connectSpy = sinon.spy(peerManager["lockPeerIfNeeded"]); + const disconnectSpy = sinon.spy(peerManager["requestRenew"]); + peerManager["lockPeerIfNeeded"] = connectSpy; + peerManager["requestRenew"] = disconnectSpy; + + libp2p.dispatchEvent(new CustomEvent("peer:connect", { detail: "1" })); + libp2p.dispatchEvent(new CustomEvent("peer:disconnect", { detail: "1" })); + + expect(connectSpy.calledOnce).to.be.true; + expect(disconnectSpy.calledOnce).to.be.true; + }); +}); + +function mockLibp2p(): Libp2p { + const peerStore = { + get: (id: any) => Promise.resolve(mockPeer(id.toString())) + }; + + const events = new EventTarget(); + + return { + peerStore, + addEventListener: (event: string, handler: EventListener) => + events.addEventListener(event, handler), + removeEventListener: (event: string, handler: EventListener) => + events.removeEventListener(event, handler), + dispatchEvent: (event: Event) => events.dispatchEvent(event), + getConnections: () => [], + components: { + events, + peerStore + } + } as unknown as Libp2p; +} + +function mockPeer(id: string): Peer { + return { + id, + protocols: [] + } as unknown as Peer; +} + +function mockConnection(id: string, locked: boolean): Connection { + return { + remotePeer: { + toString: () => id, + equals: (other: string | PeerId) => + (typeof other === "string" ? other.toString() : other) === id + }, + status: "open", + tags: locked ? ["peer-manager-lock"] : [] + } as unknown as Connection; +} diff --git a/packages/sdk/src/protocols/peer_manager.ts b/packages/sdk/src/protocols/peer_manager/peer_manager.ts similarity index 100% rename from packages/sdk/src/protocols/peer_manager.ts rename to packages/sdk/src/protocols/peer_manager/peer_manager.ts diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index 909e830c93..dcbdd53295 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -12,7 +12,7 @@ import { import { messageHash } from "@waku/message-hash"; import { ensurePubsubTopicIsConfigured, isDefined, Logger } from "@waku/utils"; -import { PeerManager } from "../peer_manager.js"; +import { PeerManager } from "../peer_manager/index.js"; const log = new Logger("waku:store:sdk"); diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index 97ab276ed8..8d1800dedb 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -18,7 +18,7 @@ import { Logger } from "@waku/utils"; import { wakuFilter } from "../protocols/filter/index.js"; import { wakuLightPush } from "../protocols/light_push/index.js"; -import { PeerManager } from "../protocols/peer_manager.js"; +import { PeerManager } from "../protocols/peer_manager/index.js"; import { wakuStore } from "../protocols/store/index.js"; import { waitForRemotePeer } from "./wait_for_remote_peer.js"; From e84d0dba7df90b461abaa8ca51eaf14bc645403c Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 23:23:45 +0100 Subject: [PATCH 20/22] create named files for protocols --- packages/sdk/src/protocols/filter/filter.ts | 293 +++++++++++++++++ packages/sdk/src/protocols/filter/index.ts | 294 +----------------- .../sdk/src/protocols/light_push/index.ts | 2 +- packages/sdk/src/protocols/store/index.ts | 268 +--------------- packages/sdk/src/protocols/store/store.ts | 267 ++++++++++++++++ 5 files changed, 563 insertions(+), 561 deletions(-) create mode 100644 packages/sdk/src/protocols/filter/filter.ts create mode 100644 packages/sdk/src/protocols/store/store.ts diff --git a/packages/sdk/src/protocols/filter/filter.ts b/packages/sdk/src/protocols/filter/filter.ts new file mode 100644 index 0000000000..1f7c513b7f --- /dev/null +++ b/packages/sdk/src/protocols/filter/filter.ts @@ -0,0 +1,293 @@ +import { ConnectionManager, FilterCore } from "@waku/core"; +import type { + Callback, + CreateSubscriptionResult, + FilterProtocolOptions, + IAsyncIterator, + IDecodedMessage, + IDecoder, + IFilter, + ILightPush, + Libp2p, + PubsubTopic, + SubscribeResult, + Unsubscribe +} from "@waku/interfaces"; +import { NetworkConfig, ProtocolError } from "@waku/interfaces"; +import { + ensurePubsubTopicIsConfigured, + groupByContentTopic, + Logger, + shardInfoToPubsubTopics, + toAsyncIterator +} from "@waku/utils"; + +import { PeerManager } from "../peer_manager/index.js"; + +import { Subscription } from "./subscription.js"; +import { buildConfig } from "./utils.js"; + +const log = new Logger("sdk:filter"); + +class Filter implements IFilter { + public readonly protocol: FilterCore; + + private readonly config: FilterProtocolOptions; + private activeSubscriptions = new Map(); + + public constructor( + private connectionManager: ConnectionManager, + private libp2p: Libp2p, + private peerManager: PeerManager, + private lightPush?: ILightPush, + config?: Partial + ) { + this.config = buildConfig(config); + + this.protocol = new FilterCore( + async (pubsubTopic, wakuMessage, peerIdStr) => { + const subscription = this.getActiveSubscription(pubsubTopic); + if (!subscription) { + log.error( + `No subscription locally registered for topic ${pubsubTopic}` + ); + return; + } + + await subscription.processIncomingMessage(wakuMessage, peerIdStr); + }, + + connectionManager.pubsubTopics, + libp2p + ); + + this.activeSubscriptions = new Map(); + } + + /** + * Opens a subscription with the Filter protocol using the provided decoders and callback. + * This method combines the functionality of creating a subscription and subscribing to it. + * + * @param {IDecoder | IDecoder[]} decoders - A single decoder or an array of decoders to use for decoding messages. + * @param {Callback} callback - The callback function to be invoked with decoded messages. + * + * @returns {Promise} A promise that resolves to an object containing: + * - subscription: The created subscription object if successful, or null if failed. + * - error: A ProtocolError if the subscription creation failed, or null if successful. + * - results: An object containing arrays of failures and successes from the subscription process. + * Only present if the subscription was created successfully. + * + * @throws {Error} If there's an unexpected error during the subscription process. + * + * @remarks + * This method attempts to create a subscription using the pubsub topic derived from the provided decoders, + * then tries to subscribe using the created subscription. The return value should be interpreted as follows: + * - If `subscription` is null and `error` is non-null, a critical error occurred and the subscription failed completely. + * - If `subscription` is non-null and `error` is null, the subscription was created successfully. + * In this case, check the `results` field for detailed information about successes and failures during the subscription process. + * - Even if the subscription was created successfully, there might be some failures in the results. + * + * @example + * ```typescript + * const {subscription, error, results} = await waku.filter.subscribe(decoders, callback); + * if (!subscription || error) { + * console.error("Failed to create subscription:", error); + * } + * console.log("Subscription created successfully"); + * if (results.failures.length > 0) { + * console.warn("Some errors occurred during subscription:", results.failures); + * } + * console.log("Successful subscriptions:", results.successes); + * + * ``` + */ + public async subscribe( + decoders: IDecoder | IDecoder[], + callback: Callback + ): Promise { + const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); + + if (uniquePubsubTopics.length !== 1) { + return { + subscription: null, + error: ProtocolError.INVALID_DECODER_TOPICS, + results: null + }; + } + + const pubsubTopic = uniquePubsubTopics[0]; + + const { subscription, error } = await this.createSubscription(pubsubTopic); + + if (error) { + return { + subscription: null, + error: error, + results: null + }; + } + + const { failures, successes } = await subscription.subscribe( + decoders, + callback + ); + return { + subscription, + error: null, + results: { + failures: failures, + successes: successes + } + }; + } + + /** + * Creates a new subscription to the given pubsub topic. + * The subscription is made to multiple peers for decentralization. + * @param pubsubTopicShardInfo The pubsub topic to subscribe to. + * @returns The subscription object. + */ + private async createSubscription( + pubsubTopicShardInfo: NetworkConfig | PubsubTopic + ): Promise { + const pubsubTopic = + typeof pubsubTopicShardInfo == "string" + ? pubsubTopicShardInfo + : shardInfoToPubsubTopics(pubsubTopicShardInfo)?.[0]; + + ensurePubsubTopicIsConfigured(pubsubTopic, this.protocol.pubsubTopics); + + const peers = await this.peerManager.getPeers(); + if (peers.length === 0) { + return { + error: ProtocolError.NO_PEER_AVAILABLE, + subscription: null + }; + } + + log.info( + `Creating filter subscription with ${peers.length} peers: `, + peers.map((peer) => peer.id.toString()) + ); + + const subscription = + this.getActiveSubscription(pubsubTopic) ?? + this.setActiveSubscription( + pubsubTopic, + new Subscription( + pubsubTopic, + this.protocol, + this.connectionManager, + this.peerManager, + this.libp2p, + this.config, + this.lightPush + ) + ); + + return { + error: null, + subscription + }; + } + + /** + * This method is used to satisfy the `IReceiver` interface. + * + * @hidden + * + * @param decoders The decoders to use for the subscription. + * @param callback The callback function to use for the subscription. + * @param opts Optional protocol options for the subscription. + * + * @returns A Promise that resolves to a function that unsubscribes from the subscription. + * + * @remarks + * This method should not be used directly. + * Instead, use `createSubscription` to create a new subscription. + */ + public async subscribeWithUnsubscribe( + decoders: IDecoder | IDecoder[], + callback: Callback + ): Promise { + const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); + + if (uniquePubsubTopics.length === 0) { + throw Error( + "Failed to subscribe: no pubsubTopic found on decoders provided." + ); + } + + if (uniquePubsubTopics.length > 1) { + throw Error( + "Failed to subscribe: all decoders should have the same pubsub topic. Use createSubscription to be more agile." + ); + } + + const { subscription, error } = await this.createSubscription( + uniquePubsubTopics[0] + ); + + if (error) { + throw Error(`Failed to create subscription: ${error}`); + } + + await subscription.subscribe(decoders, callback); + + const contentTopics = Array.from( + groupByContentTopic( + Array.isArray(decoders) ? decoders : [decoders] + ).keys() + ); + + return async () => { + await subscription.unsubscribe(contentTopics); + }; + } + + public toSubscriptionIterator( + decoders: IDecoder | IDecoder[] + ): Promise> { + return toAsyncIterator(this, decoders); + } + + private getActiveSubscription( + pubsubTopic: PubsubTopic + ): Subscription | undefined { + return this.activeSubscriptions.get(pubsubTopic); + } + + private setActiveSubscription( + pubsubTopic: PubsubTopic, + subscription: Subscription + ): Subscription { + this.activeSubscriptions.set(pubsubTopic, subscription); + return subscription; + } + + private getUniquePubsubTopics( + decoders: IDecoder | IDecoder[] + ): string[] { + if (!Array.isArray(decoders)) { + return [decoders.pubsubTopic]; + } + + if (decoders.length === 0) { + return []; + } + + const pubsubTopics = new Set(decoders.map((d) => d.pubsubTopic)); + + return [...pubsubTopics]; + } +} + +export function wakuFilter( + connectionManager: ConnectionManager, + peerManager: PeerManager, + lightPush?: ILightPush, + config?: Partial +): (libp2p: Libp2p) => IFilter { + return (libp2p: Libp2p) => + new Filter(connectionManager, libp2p, peerManager, lightPush, config); +} diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/protocols/filter/index.ts index 1f7c513b7f..63037739e4 100644 --- a/packages/sdk/src/protocols/filter/index.ts +++ b/packages/sdk/src/protocols/filter/index.ts @@ -1,293 +1 @@ -import { ConnectionManager, FilterCore } from "@waku/core"; -import type { - Callback, - CreateSubscriptionResult, - FilterProtocolOptions, - IAsyncIterator, - IDecodedMessage, - IDecoder, - IFilter, - ILightPush, - Libp2p, - PubsubTopic, - SubscribeResult, - Unsubscribe -} from "@waku/interfaces"; -import { NetworkConfig, ProtocolError } from "@waku/interfaces"; -import { - ensurePubsubTopicIsConfigured, - groupByContentTopic, - Logger, - shardInfoToPubsubTopics, - toAsyncIterator -} from "@waku/utils"; - -import { PeerManager } from "../peer_manager/index.js"; - -import { Subscription } from "./subscription.js"; -import { buildConfig } from "./utils.js"; - -const log = new Logger("sdk:filter"); - -class Filter implements IFilter { - public readonly protocol: FilterCore; - - private readonly config: FilterProtocolOptions; - private activeSubscriptions = new Map(); - - public constructor( - private connectionManager: ConnectionManager, - private libp2p: Libp2p, - private peerManager: PeerManager, - private lightPush?: ILightPush, - config?: Partial - ) { - this.config = buildConfig(config); - - this.protocol = new FilterCore( - async (pubsubTopic, wakuMessage, peerIdStr) => { - const subscription = this.getActiveSubscription(pubsubTopic); - if (!subscription) { - log.error( - `No subscription locally registered for topic ${pubsubTopic}` - ); - return; - } - - await subscription.processIncomingMessage(wakuMessage, peerIdStr); - }, - - connectionManager.pubsubTopics, - libp2p - ); - - this.activeSubscriptions = new Map(); - } - - /** - * Opens a subscription with the Filter protocol using the provided decoders and callback. - * This method combines the functionality of creating a subscription and subscribing to it. - * - * @param {IDecoder | IDecoder[]} decoders - A single decoder or an array of decoders to use for decoding messages. - * @param {Callback} callback - The callback function to be invoked with decoded messages. - * - * @returns {Promise} A promise that resolves to an object containing: - * - subscription: The created subscription object if successful, or null if failed. - * - error: A ProtocolError if the subscription creation failed, or null if successful. - * - results: An object containing arrays of failures and successes from the subscription process. - * Only present if the subscription was created successfully. - * - * @throws {Error} If there's an unexpected error during the subscription process. - * - * @remarks - * This method attempts to create a subscription using the pubsub topic derived from the provided decoders, - * then tries to subscribe using the created subscription. The return value should be interpreted as follows: - * - If `subscription` is null and `error` is non-null, a critical error occurred and the subscription failed completely. - * - If `subscription` is non-null and `error` is null, the subscription was created successfully. - * In this case, check the `results` field for detailed information about successes and failures during the subscription process. - * - Even if the subscription was created successfully, there might be some failures in the results. - * - * @example - * ```typescript - * const {subscription, error, results} = await waku.filter.subscribe(decoders, callback); - * if (!subscription || error) { - * console.error("Failed to create subscription:", error); - * } - * console.log("Subscription created successfully"); - * if (results.failures.length > 0) { - * console.warn("Some errors occurred during subscription:", results.failures); - * } - * console.log("Successful subscriptions:", results.successes); - * - * ``` - */ - public async subscribe( - decoders: IDecoder | IDecoder[], - callback: Callback - ): Promise { - const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); - - if (uniquePubsubTopics.length !== 1) { - return { - subscription: null, - error: ProtocolError.INVALID_DECODER_TOPICS, - results: null - }; - } - - const pubsubTopic = uniquePubsubTopics[0]; - - const { subscription, error } = await this.createSubscription(pubsubTopic); - - if (error) { - return { - subscription: null, - error: error, - results: null - }; - } - - const { failures, successes } = await subscription.subscribe( - decoders, - callback - ); - return { - subscription, - error: null, - results: { - failures: failures, - successes: successes - } - }; - } - - /** - * Creates a new subscription to the given pubsub topic. - * The subscription is made to multiple peers for decentralization. - * @param pubsubTopicShardInfo The pubsub topic to subscribe to. - * @returns The subscription object. - */ - private async createSubscription( - pubsubTopicShardInfo: NetworkConfig | PubsubTopic - ): Promise { - const pubsubTopic = - typeof pubsubTopicShardInfo == "string" - ? pubsubTopicShardInfo - : shardInfoToPubsubTopics(pubsubTopicShardInfo)?.[0]; - - ensurePubsubTopicIsConfigured(pubsubTopic, this.protocol.pubsubTopics); - - const peers = await this.peerManager.getPeers(); - if (peers.length === 0) { - return { - error: ProtocolError.NO_PEER_AVAILABLE, - subscription: null - }; - } - - log.info( - `Creating filter subscription with ${peers.length} peers: `, - peers.map((peer) => peer.id.toString()) - ); - - const subscription = - this.getActiveSubscription(pubsubTopic) ?? - this.setActiveSubscription( - pubsubTopic, - new Subscription( - pubsubTopic, - this.protocol, - this.connectionManager, - this.peerManager, - this.libp2p, - this.config, - this.lightPush - ) - ); - - return { - error: null, - subscription - }; - } - - /** - * This method is used to satisfy the `IReceiver` interface. - * - * @hidden - * - * @param decoders The decoders to use for the subscription. - * @param callback The callback function to use for the subscription. - * @param opts Optional protocol options for the subscription. - * - * @returns A Promise that resolves to a function that unsubscribes from the subscription. - * - * @remarks - * This method should not be used directly. - * Instead, use `createSubscription` to create a new subscription. - */ - public async subscribeWithUnsubscribe( - decoders: IDecoder | IDecoder[], - callback: Callback - ): Promise { - const uniquePubsubTopics = this.getUniquePubsubTopics(decoders); - - if (uniquePubsubTopics.length === 0) { - throw Error( - "Failed to subscribe: no pubsubTopic found on decoders provided." - ); - } - - if (uniquePubsubTopics.length > 1) { - throw Error( - "Failed to subscribe: all decoders should have the same pubsub topic. Use createSubscription to be more agile." - ); - } - - const { subscription, error } = await this.createSubscription( - uniquePubsubTopics[0] - ); - - if (error) { - throw Error(`Failed to create subscription: ${error}`); - } - - await subscription.subscribe(decoders, callback); - - const contentTopics = Array.from( - groupByContentTopic( - Array.isArray(decoders) ? decoders : [decoders] - ).keys() - ); - - return async () => { - await subscription.unsubscribe(contentTopics); - }; - } - - public toSubscriptionIterator( - decoders: IDecoder | IDecoder[] - ): Promise> { - return toAsyncIterator(this, decoders); - } - - private getActiveSubscription( - pubsubTopic: PubsubTopic - ): Subscription | undefined { - return this.activeSubscriptions.get(pubsubTopic); - } - - private setActiveSubscription( - pubsubTopic: PubsubTopic, - subscription: Subscription - ): Subscription { - this.activeSubscriptions.set(pubsubTopic, subscription); - return subscription; - } - - private getUniquePubsubTopics( - decoders: IDecoder | IDecoder[] - ): string[] { - if (!Array.isArray(decoders)) { - return [decoders.pubsubTopic]; - } - - if (decoders.length === 0) { - return []; - } - - const pubsubTopics = new Set(decoders.map((d) => d.pubsubTopic)); - - return [...pubsubTopics]; - } -} - -export function wakuFilter( - connectionManager: ConnectionManager, - peerManager: PeerManager, - lightPush?: ILightPush, - config?: Partial -): (libp2p: Libp2p) => IFilter { - return (libp2p: Libp2p) => - new Filter(connectionManager, libp2p, peerManager, lightPush, config); -} +export { wakuFilter } from "./filter.js"; diff --git a/packages/sdk/src/protocols/light_push/index.ts b/packages/sdk/src/protocols/light_push/index.ts index 57c03d3296..f0cc05e922 100644 --- a/packages/sdk/src/protocols/light_push/index.ts +++ b/packages/sdk/src/protocols/light_push/index.ts @@ -1 +1 @@ -export { wakuLightPush } from "./light_push.js"; +export { LightPush, wakuLightPush } from "./light_push.js"; diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/protocols/store/index.ts index dcbdd53295..abd28fe17b 100644 --- a/packages/sdk/src/protocols/store/index.ts +++ b/packages/sdk/src/protocols/store/index.ts @@ -1,267 +1 @@ -import type { Peer } from "@libp2p/interface"; -import { ConnectionManager, StoreCore } from "@waku/core"; -import { - IDecodedMessage, - IDecoder, - IStore, - Libp2p, - QueryRequestParams, - StoreCursor, - StoreProtocolOptions -} from "@waku/interfaces"; -import { messageHash } from "@waku/message-hash"; -import { ensurePubsubTopicIsConfigured, isDefined, Logger } from "@waku/utils"; - -import { PeerManager } from "../peer_manager/index.js"; - -const log = new Logger("waku:store:sdk"); - -/** - * StoreSDK is an implementation of the IStoreSDK interface. - * It provides methods to interact with the Waku Store protocol. - */ -export class Store implements IStore { - public readonly protocol: StoreCore; - - public constructor( - private connectionManager: ConnectionManager, - libp2p: Libp2p, - private peerManager: PeerManager, - private options?: Partial - ) { - this.protocol = new StoreCore(connectionManager.pubsubTopics, libp2p); - } - - /** - * Queries the Waku Store for historical messages using the provided decoders and options. - * Returns an asynchronous generator that yields promises of decoded messages. - * - * @param decoders - An array of message decoders. - * @param options - Optional query parameters. - * @returns An asynchronous generator of promises of decoded messages. - * @throws If no peers are available to query or if an error occurs during the query. - */ - public async *queryGenerator( - decoders: IDecoder[], - options?: Partial - ): AsyncGenerator[]> { - const { pubsubTopic, contentTopics, decodersAsMap } = - this.validateDecodersAndPubsubTopic(decoders); - - const queryOpts = { - pubsubTopic, - contentTopics, - includeData: true, - paginationForward: true, - ...options - }; - - const peer = await this.getPeerToUse(); - - if (!peer) { - log.error("No peers available to query"); - throw new Error("No peers available to query"); - } - - log.info(`Querying store with options: ${JSON.stringify(options)}`); - const responseGenerator = this.protocol.queryPerPage( - queryOpts, - decodersAsMap, - peer - ); - - for await (const messages of responseGenerator) { - yield messages; - } - } - - /** - * Queries the Waku Store for historical messages and processes them with the provided callback in order. - * - * @param decoders - An array of message decoders. - * @param callback - A callback function to process each decoded message. - * @param options - Optional query parameters. - * @returns A promise that resolves when the query and message processing are completed. - */ - public async queryWithOrderedCallback( - decoders: IDecoder[], - callback: (message: T) => Promise | boolean | void, - options?: Partial - ): Promise { - log.info("Querying store with ordered callback"); - for await (const promises of this.queryGenerator(decoders, options)) { - if (await this.processMessages(promises, callback)) break; - } - } - - /** - * Queries the Waku Store for historical messages and processes them with the provided callback using promises. - * - * @param decoders - An array of message decoders. - * @param callback - A callback function to process each promise of a decoded message. - * @param options - Optional query parameters. - * @returns A promise that resolves when the query and message processing are completed. - */ - public async queryWithPromiseCallback( - decoders: IDecoder[], - callback: ( - message: Promise - ) => Promise | boolean | void, - options?: Partial - ): Promise { - log.info("Querying store with promise callback"); - let abort = false; - for await (const page of this.queryGenerator(decoders, options)) { - const _promises = page.map(async (msgPromise) => { - if (abort) return; - abort = Boolean(await callback(msgPromise)); - }); - - await Promise.all(_promises); - if (abort) break; - } - } - - /** - * Processes messages based on the provided callback and options. - * - * @param messages - An array of promises of decoded messages. - * @param callback - A callback function to process each decoded message. - * @returns A promise that resolves to a boolean indicating whether the processing should abort. - * @private - */ - private async processMessages( - messages: Promise[], - callback: (message: T) => Promise | boolean | void - ): Promise { - let abort = false; - const messagesOrUndef: Array = await Promise.all(messages); - const processedMessages: Array = messagesOrUndef.filter(isDefined); - - await Promise.all( - processedMessages.map(async (msg) => { - if (msg && !abort) { - abort = Boolean(await callback(msg)); - } - }) - ); - - return abort; - } - - /** - * Creates a cursor based on the provided decoded message. - * - * @param message - The decoded message. - * @returns A StoreCursor representing the message. - */ - public createCursor(message: IDecodedMessage): StoreCursor { - return messageHash(message.pubsubTopic, message); - } - - /** - * Validates the provided decoders and pubsub topic. - * - * @param decoders - An array of message decoders. - * @returns An object containing the pubsub topic, content topics, and a map of decoders. - * @throws If no decoders are provided, if multiple pubsub topics are provided, or if no decoders are found for the pubsub topic. - * @private - */ - private validateDecodersAndPubsubTopic( - decoders: IDecoder[] - ): { - pubsubTopic: string; - contentTopics: string[]; - decodersAsMap: Map>; - } { - if (decoders.length === 0) { - log.error("No decoders provided"); - throw new Error("No decoders provided"); - } - - const uniquePubsubTopicsInQuery = Array.from( - new Set(decoders.map((decoder) => decoder.pubsubTopic)) - ); - if (uniquePubsubTopicsInQuery.length > 1) { - log.error("API does not support querying multiple pubsub topics at once"); - throw new Error( - "API does not support querying multiple pubsub topics at once" - ); - } - - const pubsubTopicForQuery = uniquePubsubTopicsInQuery[0]; - - ensurePubsubTopicIsConfigured( - pubsubTopicForQuery, - this.protocol.pubsubTopics - ); - - const decodersAsMap = new Map(); - decoders.forEach((dec) => { - if (decodersAsMap.has(dec.contentTopic)) { - log.error("API does not support different decoder per content topic"); - throw new Error( - "API does not support different decoder per content topic" - ); - } - decodersAsMap.set(dec.contentTopic, dec); - }); - - const contentTopics = decoders - .filter((decoder) => decoder.pubsubTopic === pubsubTopicForQuery) - .map((dec) => dec.contentTopic); - - if (contentTopics.length === 0) { - log.error(`No decoders found for topic ${pubsubTopicForQuery}`); - throw new Error("No decoders found for topic " + pubsubTopicForQuery); - } - - return { - pubsubTopic: pubsubTopicForQuery, - contentTopics, - decodersAsMap - }; - } - - private async getPeerToUse(): Promise { - let peer: Peer | undefined; - - if (this.options?.peer) { - const connectedPeers = await this.connectionManager.getConnectedPeers(); - - peer = connectedPeers.find((p) => p.id.toString() === this.options?.peer); - - if (!peer) { - log.warn( - `Passed node to use for Store not found: ${this.options.peer}. Attempting to use random peers.` - ); - } - } - - const peers = await this.peerManager.getPeers(); - - if (peers.length > 0) { - // TODO(weboko): implement smart way of getting a peer https://github.com/waku-org/js-waku/issues/2243 - return peers[Math.floor(Math.random() * peers.length)]; - } - - log.error("No peers available to use."); - return; - } -} - -/** - * Factory function to create an instance of the StoreSDK. - * - * @param init - Partial options for protocol creation. - * @returns A function that takes a Libp2p instance and returns a StoreSDK instance. - */ -export function wakuStore( - connectionManager: ConnectionManager, - peerManager: PeerManager, - options?: Partial -): (libp2p: Libp2p) => IStore { - return (libp2p: Libp2p) => { - return new Store(connectionManager, libp2p, peerManager, options); - }; -} +export { Store, wakuStore } from "./store.js"; diff --git a/packages/sdk/src/protocols/store/store.ts b/packages/sdk/src/protocols/store/store.ts new file mode 100644 index 0000000000..dcbdd53295 --- /dev/null +++ b/packages/sdk/src/protocols/store/store.ts @@ -0,0 +1,267 @@ +import type { Peer } from "@libp2p/interface"; +import { ConnectionManager, StoreCore } from "@waku/core"; +import { + IDecodedMessage, + IDecoder, + IStore, + Libp2p, + QueryRequestParams, + StoreCursor, + StoreProtocolOptions +} from "@waku/interfaces"; +import { messageHash } from "@waku/message-hash"; +import { ensurePubsubTopicIsConfigured, isDefined, Logger } from "@waku/utils"; + +import { PeerManager } from "../peer_manager/index.js"; + +const log = new Logger("waku:store:sdk"); + +/** + * StoreSDK is an implementation of the IStoreSDK interface. + * It provides methods to interact with the Waku Store protocol. + */ +export class Store implements IStore { + public readonly protocol: StoreCore; + + public constructor( + private connectionManager: ConnectionManager, + libp2p: Libp2p, + private peerManager: PeerManager, + private options?: Partial + ) { + this.protocol = new StoreCore(connectionManager.pubsubTopics, libp2p); + } + + /** + * Queries the Waku Store for historical messages using the provided decoders and options. + * Returns an asynchronous generator that yields promises of decoded messages. + * + * @param decoders - An array of message decoders. + * @param options - Optional query parameters. + * @returns An asynchronous generator of promises of decoded messages. + * @throws If no peers are available to query or if an error occurs during the query. + */ + public async *queryGenerator( + decoders: IDecoder[], + options?: Partial + ): AsyncGenerator[]> { + const { pubsubTopic, contentTopics, decodersAsMap } = + this.validateDecodersAndPubsubTopic(decoders); + + const queryOpts = { + pubsubTopic, + contentTopics, + includeData: true, + paginationForward: true, + ...options + }; + + const peer = await this.getPeerToUse(); + + if (!peer) { + log.error("No peers available to query"); + throw new Error("No peers available to query"); + } + + log.info(`Querying store with options: ${JSON.stringify(options)}`); + const responseGenerator = this.protocol.queryPerPage( + queryOpts, + decodersAsMap, + peer + ); + + for await (const messages of responseGenerator) { + yield messages; + } + } + + /** + * Queries the Waku Store for historical messages and processes them with the provided callback in order. + * + * @param decoders - An array of message decoders. + * @param callback - A callback function to process each decoded message. + * @param options - Optional query parameters. + * @returns A promise that resolves when the query and message processing are completed. + */ + public async queryWithOrderedCallback( + decoders: IDecoder[], + callback: (message: T) => Promise | boolean | void, + options?: Partial + ): Promise { + log.info("Querying store with ordered callback"); + for await (const promises of this.queryGenerator(decoders, options)) { + if (await this.processMessages(promises, callback)) break; + } + } + + /** + * Queries the Waku Store for historical messages and processes them with the provided callback using promises. + * + * @param decoders - An array of message decoders. + * @param callback - A callback function to process each promise of a decoded message. + * @param options - Optional query parameters. + * @returns A promise that resolves when the query and message processing are completed. + */ + public async queryWithPromiseCallback( + decoders: IDecoder[], + callback: ( + message: Promise + ) => Promise | boolean | void, + options?: Partial + ): Promise { + log.info("Querying store with promise callback"); + let abort = false; + for await (const page of this.queryGenerator(decoders, options)) { + const _promises = page.map(async (msgPromise) => { + if (abort) return; + abort = Boolean(await callback(msgPromise)); + }); + + await Promise.all(_promises); + if (abort) break; + } + } + + /** + * Processes messages based on the provided callback and options. + * + * @param messages - An array of promises of decoded messages. + * @param callback - A callback function to process each decoded message. + * @returns A promise that resolves to a boolean indicating whether the processing should abort. + * @private + */ + private async processMessages( + messages: Promise[], + callback: (message: T) => Promise | boolean | void + ): Promise { + let abort = false; + const messagesOrUndef: Array = await Promise.all(messages); + const processedMessages: Array = messagesOrUndef.filter(isDefined); + + await Promise.all( + processedMessages.map(async (msg) => { + if (msg && !abort) { + abort = Boolean(await callback(msg)); + } + }) + ); + + return abort; + } + + /** + * Creates a cursor based on the provided decoded message. + * + * @param message - The decoded message. + * @returns A StoreCursor representing the message. + */ + public createCursor(message: IDecodedMessage): StoreCursor { + return messageHash(message.pubsubTopic, message); + } + + /** + * Validates the provided decoders and pubsub topic. + * + * @param decoders - An array of message decoders. + * @returns An object containing the pubsub topic, content topics, and a map of decoders. + * @throws If no decoders are provided, if multiple pubsub topics are provided, or if no decoders are found for the pubsub topic. + * @private + */ + private validateDecodersAndPubsubTopic( + decoders: IDecoder[] + ): { + pubsubTopic: string; + contentTopics: string[]; + decodersAsMap: Map>; + } { + if (decoders.length === 0) { + log.error("No decoders provided"); + throw new Error("No decoders provided"); + } + + const uniquePubsubTopicsInQuery = Array.from( + new Set(decoders.map((decoder) => decoder.pubsubTopic)) + ); + if (uniquePubsubTopicsInQuery.length > 1) { + log.error("API does not support querying multiple pubsub topics at once"); + throw new Error( + "API does not support querying multiple pubsub topics at once" + ); + } + + const pubsubTopicForQuery = uniquePubsubTopicsInQuery[0]; + + ensurePubsubTopicIsConfigured( + pubsubTopicForQuery, + this.protocol.pubsubTopics + ); + + const decodersAsMap = new Map(); + decoders.forEach((dec) => { + if (decodersAsMap.has(dec.contentTopic)) { + log.error("API does not support different decoder per content topic"); + throw new Error( + "API does not support different decoder per content topic" + ); + } + decodersAsMap.set(dec.contentTopic, dec); + }); + + const contentTopics = decoders + .filter((decoder) => decoder.pubsubTopic === pubsubTopicForQuery) + .map((dec) => dec.contentTopic); + + if (contentTopics.length === 0) { + log.error(`No decoders found for topic ${pubsubTopicForQuery}`); + throw new Error("No decoders found for topic " + pubsubTopicForQuery); + } + + return { + pubsubTopic: pubsubTopicForQuery, + contentTopics, + decodersAsMap + }; + } + + private async getPeerToUse(): Promise { + let peer: Peer | undefined; + + if (this.options?.peer) { + const connectedPeers = await this.connectionManager.getConnectedPeers(); + + peer = connectedPeers.find((p) => p.id.toString() === this.options?.peer); + + if (!peer) { + log.warn( + `Passed node to use for Store not found: ${this.options.peer}. Attempting to use random peers.` + ); + } + } + + const peers = await this.peerManager.getPeers(); + + if (peers.length > 0) { + // TODO(weboko): implement smart way of getting a peer https://github.com/waku-org/js-waku/issues/2243 + return peers[Math.floor(Math.random() * peers.length)]; + } + + log.error("No peers available to use."); + return; + } +} + +/** + * Factory function to create an instance of the StoreSDK. + * + * @param init - Partial options for protocol creation. + * @returns A function that takes a Libp2p instance and returns a StoreSDK instance. + */ +export function wakuStore( + connectionManager: ConnectionManager, + peerManager: PeerManager, + options?: Partial +): (libp2p: Libp2p) => IStore { + return (libp2p: Libp2p) => { + return new Store(connectionManager, libp2p, peerManager, options); + }; +} From 8aac8176e650fb10f568fbfc0a156273c28c41d8 Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 23:43:08 +0100 Subject: [PATCH 21/22] create named files, simplify project structure --- package-lock.json | 35 ++ packages/core/src/lib/filter/filter.ts | 315 +++++++++++++++++ packages/core/src/lib/filter/index.ts | 316 +----------------- packages/core/src/lib/light_push/index.ts | 190 +---------- .../core/src/lib/light_push/light_push.ts | 189 +++++++++++ packages/core/src/lib/metadata/index.ts | 183 +--------- packages/core/src/lib/metadata/metadata.ts | 182 ++++++++++ packages/core/src/lib/store/index.ts | 137 +------- packages/core/src/lib/store/store.ts | 136 ++++++++ .../src/{protocols => }/filter/constants.ts | 0 .../sdk/src/{protocols => }/filter/filter.ts | 0 .../sdk/src/{protocols => }/filter/index.ts | 0 .../{protocols => }/filter/subscription.ts | 0 .../filter/subscription_monitor.ts | 0 .../sdk/src/{protocols => }/filter/utils.ts | 0 packages/sdk/src/index.ts | 6 +- .../src/{protocols => }/light_push/index.ts | 0 .../light_push/light_push.spec.ts | 0 .../{protocols => }/light_push/light_push.ts | 0 .../src/{protocols => }/peer_manager/index.ts | 0 .../peer_manager/peer_manager.spec.ts | 0 .../peer_manager/peer_manager.ts | 0 .../sdk/src/{protocols => }/store/index.ts | 0 .../sdk/src/{protocols => }/store/store.ts | 0 packages/sdk/src/waku/waku.ts | 8 +- 25 files changed, 868 insertions(+), 829 deletions(-) create mode 100644 packages/core/src/lib/filter/filter.ts create mode 100644 packages/core/src/lib/light_push/light_push.ts create mode 100644 packages/core/src/lib/metadata/metadata.ts create mode 100644 packages/core/src/lib/store/store.ts rename packages/sdk/src/{protocols => }/filter/constants.ts (100%) rename packages/sdk/src/{protocols => }/filter/filter.ts (100%) rename packages/sdk/src/{protocols => }/filter/index.ts (100%) rename packages/sdk/src/{protocols => }/filter/subscription.ts (100%) rename packages/sdk/src/{protocols => }/filter/subscription_monitor.ts (100%) rename packages/sdk/src/{protocols => }/filter/utils.ts (100%) rename packages/sdk/src/{protocols => }/light_push/index.ts (100%) rename packages/sdk/src/{protocols => }/light_push/light_push.spec.ts (100%) rename packages/sdk/src/{protocols => }/light_push/light_push.ts (100%) rename packages/sdk/src/{protocols => }/peer_manager/index.ts (100%) rename packages/sdk/src/{protocols => }/peer_manager/peer_manager.spec.ts (100%) rename packages/sdk/src/{protocols => }/peer_manager/peer_manager.ts (100%) rename packages/sdk/src/{protocols => }/store/index.ts (100%) rename packages/sdk/src/{protocols => }/store/store.ts (100%) diff --git a/package-lock.json b/package-lock.json index 763f7a1c3c..4aff4d781a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6313,6 +6313,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" @@ -13368,6 +13369,7 @@ "version": "18.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", @@ -13391,6 +13393,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -13411,12 +13414,14 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/cacache/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -13432,6 +13437,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -13897,6 +13903,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -19311,6 +19318,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -20465,6 +20473,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -26178,6 +26187,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -26190,6 +26200,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -26202,6 +26213,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -26214,6 +26226,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -26226,6 +26239,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -26238,6 +26252,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -26251,6 +26266,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -27019,6 +27035,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", + "dev": true, "license": "ISC", "dependencies": { "hosted-git-info": "^3.0.2", @@ -27031,12 +27048,14 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", + "dev": true, "license": "MIT" }, "node_modules/npm-package-arg/node_modules/hosted-git-info": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -27049,6 +27068,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -27061,6 +27081,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver" @@ -27070,6 +27091,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", + "dev": true, "license": "ISC", "dependencies": { "builtins": "^1.0.3" @@ -30718,6 +30740,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -30737,6 +30760,7 @@ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "dependencies": { "os-homedir": "^1.0.0", @@ -32935,6 +32959,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -33564,6 +33589,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==", + "dev": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" } @@ -37416,6 +37442,7 @@ "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -38138,6 +38165,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -38182,6 +38210,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -38194,6 +38223,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -38206,6 +38236,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -38215,6 +38246,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -39615,6 +39647,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" @@ -39627,6 +39660,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" @@ -40594,6 +40628,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", diff --git a/packages/core/src/lib/filter/filter.ts b/packages/core/src/lib/filter/filter.ts new file mode 100644 index 0000000000..09bbef9e62 --- /dev/null +++ b/packages/core/src/lib/filter/filter.ts @@ -0,0 +1,315 @@ +import type { Peer, Stream } from "@libp2p/interface"; +import type { IncomingStreamData } from "@libp2p/interface-internal"; +import { + type ContentTopic, + type CoreProtocolResult, + type IBaseProtocolCore, + type Libp2p, + ProtocolError, + type PubsubTopic +} from "@waku/interfaces"; +import { WakuMessage } from "@waku/proto"; +import { Logger } from "@waku/utils"; +import all from "it-all"; +import * as lp from "it-length-prefixed"; +import { pipe } from "it-pipe"; +import { Uint8ArrayList } from "uint8arraylist"; + +import { BaseProtocol } from "../base_protocol.js"; + +import { + FilterPushRpc, + FilterSubscribeResponse, + FilterSubscribeRpc +} from "./filter_rpc.js"; + +const log = new Logger("filter:v2"); + +export const FilterCodecs = { + SUBSCRIBE: "/vac/waku/filter-subscribe/2.0.0-beta1", + PUSH: "/vac/waku/filter-push/2.0.0-beta1" +}; + +export class FilterCore extends BaseProtocol implements IBaseProtocolCore { + public constructor( + private handleIncomingMessage: ( + pubsubTopic: PubsubTopic, + wakuMessage: WakuMessage, + peerIdStr: string + ) => Promise, + public readonly pubsubTopics: PubsubTopic[], + libp2p: Libp2p + ) { + super(FilterCodecs.SUBSCRIBE, libp2p.components, pubsubTopics); + + libp2p + .handle(FilterCodecs.PUSH, this.onRequest.bind(this), { + maxInboundStreams: 100 + }) + .catch((e) => { + log.error("Failed to register ", FilterCodecs.PUSH, e); + }); + } + + public async subscribe( + pubsubTopic: PubsubTopic, + peer: Peer, + contentTopics: ContentTopic[] + ): Promise { + const stream = await this.getStream(peer); + + const request = FilterSubscribeRpc.createSubscribeRequest( + pubsubTopic, + contentTopics + ); + + let res: Uint8ArrayList[] | undefined; + try { + res = await pipe( + [request.encode()], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + } catch (error) { + log.error("Failed to send subscribe request", error); + return { + success: null, + failure: { + error: ProtocolError.GENERIC_FAIL, + peerId: peer.id + } + }; + } + + const { statusCode, requestId, statusDesc } = + FilterSubscribeResponse.decode(res[0].slice()); + + if (statusCode < 200 || statusCode >= 300) { + log.error( + `Filter subscribe request ${requestId} failed with status code ${statusCode}: ${statusDesc}` + ); + return { + failure: { + error: ProtocolError.REMOTE_PEER_REJECTED, + peerId: peer.id + }, + success: null + }; + } + + return { + failure: null, + success: peer.id + }; + } + + public async unsubscribe( + pubsubTopic: PubsubTopic, + peer: Peer, + contentTopics: ContentTopic[] + ): Promise { + let stream: Stream | undefined; + try { + stream = await this.getStream(peer); + } catch (error) { + log.error( + `Failed to get a stream for remote peer${peer.id.toString()}`, + error + ); + return { + success: null, + failure: { + error: ProtocolError.NO_STREAM_AVAILABLE, + peerId: peer.id + } + }; + } + + const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest( + pubsubTopic, + contentTopics + ); + + try { + await pipe([unsubscribeRequest.encode()], lp.encode, stream.sink); + } catch (error) { + log.error("Failed to send unsubscribe request", error); + return { + success: null, + failure: { + error: ProtocolError.GENERIC_FAIL, + peerId: peer.id + } + }; + } + + return { + success: peer.id, + failure: null + }; + } + + public async unsubscribeAll( + pubsubTopic: PubsubTopic, + peer: Peer + ): Promise { + const stream = await this.getStream(peer); + + const request = FilterSubscribeRpc.createUnsubscribeAllRequest(pubsubTopic); + + const res = await pipe( + [request.encode()], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + + if (!res || !res.length) { + return { + failure: { + error: ProtocolError.NO_RESPONSE, + peerId: peer.id + }, + success: null + }; + } + + const { statusCode, requestId, statusDesc } = + FilterSubscribeResponse.decode(res[0].slice()); + + if (statusCode < 200 || statusCode >= 300) { + log.error( + `Filter unsubscribe all request ${requestId} failed with status code ${statusCode}: ${statusDesc}` + ); + return { + failure: { + error: ProtocolError.REMOTE_PEER_REJECTED, + peerId: peer.id + }, + success: null + }; + } + + return { + failure: null, + success: peer.id + }; + } + + public async ping(peer: Peer): Promise { + let stream: Stream | undefined; + try { + stream = await this.getStream(peer); + } catch (error) { + log.error( + `Failed to get a stream for remote peer${peer.id.toString()}`, + error + ); + return { + success: null, + failure: { + error: ProtocolError.NO_STREAM_AVAILABLE, + peerId: peer.id + } + }; + } + + const request = FilterSubscribeRpc.createSubscriberPingRequest(); + + let res: Uint8ArrayList[] | undefined; + try { + res = await pipe( + [request.encode()], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + } catch (error) { + log.error("Failed to send ping request", error); + return { + success: null, + failure: { + error: ProtocolError.GENERIC_FAIL, + peerId: peer.id + } + }; + } + + if (!res || !res.length) { + return { + success: null, + failure: { + error: ProtocolError.NO_RESPONSE, + peerId: peer.id + } + }; + } + + const { statusCode, requestId, statusDesc } = + FilterSubscribeResponse.decode(res[0].slice()); + + if (statusCode < 200 || statusCode >= 300) { + log.error( + `Filter ping request ${requestId} failed with status code ${statusCode}: ${statusDesc}` + ); + return { + success: null, + failure: { + error: ProtocolError.REMOTE_PEER_REJECTED, + peerId: peer.id + } + }; + } + return { + success: peer.id, + failure: null + }; + } + + private onRequest(streamData: IncomingStreamData): void { + const { connection, stream } = streamData; + const { remotePeer } = connection; + log.info(`Received message from ${remotePeer.toString()}`); + try { + pipe(stream, lp.decode, async (source) => { + for await (const bytes of source) { + const response = FilterPushRpc.decode(bytes.slice()); + + const { pubsubTopic, wakuMessage } = response; + + if (!wakuMessage) { + log.error("Received empty message"); + return; + } + + if (!pubsubTopic) { + log.error("Pubsub topic missing from push message"); + return; + } + + await this.handleIncomingMessage( + pubsubTopic, + wakuMessage, + connection.remotePeer.toString() + ); + } + }).then( + () => { + log.info("Receiving pipe closed."); + }, + async (e) => { + log.error( + `Error with receiving pipe on peer:${connection.remotePeer.toString()} -- stream:${stream.id} -- protocol:${stream.protocol}: `, + e + ); + } + ); + } catch (e) { + log.error("Error decoding message", e); + } + } +} diff --git a/packages/core/src/lib/filter/index.ts b/packages/core/src/lib/filter/index.ts index 09bbef9e62..8ed3901297 100644 --- a/packages/core/src/lib/filter/index.ts +++ b/packages/core/src/lib/filter/index.ts @@ -1,315 +1 @@ -import type { Peer, Stream } from "@libp2p/interface"; -import type { IncomingStreamData } from "@libp2p/interface-internal"; -import { - type ContentTopic, - type CoreProtocolResult, - type IBaseProtocolCore, - type Libp2p, - ProtocolError, - type PubsubTopic -} from "@waku/interfaces"; -import { WakuMessage } from "@waku/proto"; -import { Logger } from "@waku/utils"; -import all from "it-all"; -import * as lp from "it-length-prefixed"; -import { pipe } from "it-pipe"; -import { Uint8ArrayList } from "uint8arraylist"; - -import { BaseProtocol } from "../base_protocol.js"; - -import { - FilterPushRpc, - FilterSubscribeResponse, - FilterSubscribeRpc -} from "./filter_rpc.js"; - -const log = new Logger("filter:v2"); - -export const FilterCodecs = { - SUBSCRIBE: "/vac/waku/filter-subscribe/2.0.0-beta1", - PUSH: "/vac/waku/filter-push/2.0.0-beta1" -}; - -export class FilterCore extends BaseProtocol implements IBaseProtocolCore { - public constructor( - private handleIncomingMessage: ( - pubsubTopic: PubsubTopic, - wakuMessage: WakuMessage, - peerIdStr: string - ) => Promise, - public readonly pubsubTopics: PubsubTopic[], - libp2p: Libp2p - ) { - super(FilterCodecs.SUBSCRIBE, libp2p.components, pubsubTopics); - - libp2p - .handle(FilterCodecs.PUSH, this.onRequest.bind(this), { - maxInboundStreams: 100 - }) - .catch((e) => { - log.error("Failed to register ", FilterCodecs.PUSH, e); - }); - } - - public async subscribe( - pubsubTopic: PubsubTopic, - peer: Peer, - contentTopics: ContentTopic[] - ): Promise { - const stream = await this.getStream(peer); - - const request = FilterSubscribeRpc.createSubscribeRequest( - pubsubTopic, - contentTopics - ); - - let res: Uint8ArrayList[] | undefined; - try { - res = await pipe( - [request.encode()], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - } catch (error) { - log.error("Failed to send subscribe request", error); - return { - success: null, - failure: { - error: ProtocolError.GENERIC_FAIL, - peerId: peer.id - } - }; - } - - const { statusCode, requestId, statusDesc } = - FilterSubscribeResponse.decode(res[0].slice()); - - if (statusCode < 200 || statusCode >= 300) { - log.error( - `Filter subscribe request ${requestId} failed with status code ${statusCode}: ${statusDesc}` - ); - return { - failure: { - error: ProtocolError.REMOTE_PEER_REJECTED, - peerId: peer.id - }, - success: null - }; - } - - return { - failure: null, - success: peer.id - }; - } - - public async unsubscribe( - pubsubTopic: PubsubTopic, - peer: Peer, - contentTopics: ContentTopic[] - ): Promise { - let stream: Stream | undefined; - try { - stream = await this.getStream(peer); - } catch (error) { - log.error( - `Failed to get a stream for remote peer${peer.id.toString()}`, - error - ); - return { - success: null, - failure: { - error: ProtocolError.NO_STREAM_AVAILABLE, - peerId: peer.id - } - }; - } - - const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest( - pubsubTopic, - contentTopics - ); - - try { - await pipe([unsubscribeRequest.encode()], lp.encode, stream.sink); - } catch (error) { - log.error("Failed to send unsubscribe request", error); - return { - success: null, - failure: { - error: ProtocolError.GENERIC_FAIL, - peerId: peer.id - } - }; - } - - return { - success: peer.id, - failure: null - }; - } - - public async unsubscribeAll( - pubsubTopic: PubsubTopic, - peer: Peer - ): Promise { - const stream = await this.getStream(peer); - - const request = FilterSubscribeRpc.createUnsubscribeAllRequest(pubsubTopic); - - const res = await pipe( - [request.encode()], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - - if (!res || !res.length) { - return { - failure: { - error: ProtocolError.NO_RESPONSE, - peerId: peer.id - }, - success: null - }; - } - - const { statusCode, requestId, statusDesc } = - FilterSubscribeResponse.decode(res[0].slice()); - - if (statusCode < 200 || statusCode >= 300) { - log.error( - `Filter unsubscribe all request ${requestId} failed with status code ${statusCode}: ${statusDesc}` - ); - return { - failure: { - error: ProtocolError.REMOTE_PEER_REJECTED, - peerId: peer.id - }, - success: null - }; - } - - return { - failure: null, - success: peer.id - }; - } - - public async ping(peer: Peer): Promise { - let stream: Stream | undefined; - try { - stream = await this.getStream(peer); - } catch (error) { - log.error( - `Failed to get a stream for remote peer${peer.id.toString()}`, - error - ); - return { - success: null, - failure: { - error: ProtocolError.NO_STREAM_AVAILABLE, - peerId: peer.id - } - }; - } - - const request = FilterSubscribeRpc.createSubscriberPingRequest(); - - let res: Uint8ArrayList[] | undefined; - try { - res = await pipe( - [request.encode()], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - } catch (error) { - log.error("Failed to send ping request", error); - return { - success: null, - failure: { - error: ProtocolError.GENERIC_FAIL, - peerId: peer.id - } - }; - } - - if (!res || !res.length) { - return { - success: null, - failure: { - error: ProtocolError.NO_RESPONSE, - peerId: peer.id - } - }; - } - - const { statusCode, requestId, statusDesc } = - FilterSubscribeResponse.decode(res[0].slice()); - - if (statusCode < 200 || statusCode >= 300) { - log.error( - `Filter ping request ${requestId} failed with status code ${statusCode}: ${statusDesc}` - ); - return { - success: null, - failure: { - error: ProtocolError.REMOTE_PEER_REJECTED, - peerId: peer.id - } - }; - } - return { - success: peer.id, - failure: null - }; - } - - private onRequest(streamData: IncomingStreamData): void { - const { connection, stream } = streamData; - const { remotePeer } = connection; - log.info(`Received message from ${remotePeer.toString()}`); - try { - pipe(stream, lp.decode, async (source) => { - for await (const bytes of source) { - const response = FilterPushRpc.decode(bytes.slice()); - - const { pubsubTopic, wakuMessage } = response; - - if (!wakuMessage) { - log.error("Received empty message"); - return; - } - - if (!pubsubTopic) { - log.error("Pubsub topic missing from push message"); - return; - } - - await this.handleIncomingMessage( - pubsubTopic, - wakuMessage, - connection.remotePeer.toString() - ); - } - }).then( - () => { - log.info("Receiving pipe closed."); - }, - async (e) => { - log.error( - `Error with receiving pipe on peer:${connection.remotePeer.toString()} -- stream:${stream.id} -- protocol:${stream.protocol}: `, - e - ); - } - ); - } catch (e) { - log.error("Error decoding message", e); - } - } -} +export { FilterCodecs, FilterCore } from "./filter.js"; diff --git a/packages/core/src/lib/light_push/index.ts b/packages/core/src/lib/light_push/index.ts index d649df7a21..4c5c37dccb 100644 --- a/packages/core/src/lib/light_push/index.ts +++ b/packages/core/src/lib/light_push/index.ts @@ -1,189 +1 @@ -import type { Peer, Stream } from "@libp2p/interface"; -import { - type CoreProtocolResult, - type IBaseProtocolCore, - type IEncoder, - type IMessage, - type Libp2p, - ProtocolError, - PubsubTopic, - type ThisOrThat -} from "@waku/interfaces"; -import { PushResponse } from "@waku/proto"; -import { isMessageSizeUnderCap } from "@waku/utils"; -import { Logger } from "@waku/utils"; -import all from "it-all"; -import * as lp from "it-length-prefixed"; -import { pipe } from "it-pipe"; -import { Uint8ArrayList } from "uint8arraylist"; - -import { BaseProtocol } from "../base_protocol.js"; - -import { PushRpc } from "./push_rpc.js"; -import { isRLNResponseError, matchRLNErrorMessage } from "./utils.js"; - -const log = new Logger("light-push"); - -export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1"; -export { PushResponse }; - -type PreparePushMessageResult = ThisOrThat<"query", PushRpc>; - -/** - * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/). - */ -export class LightPushCore extends BaseProtocol implements IBaseProtocolCore { - public constructor( - public readonly pubsubTopics: PubsubTopic[], - libp2p: Libp2p - ) { - super(LightPushCodec, libp2p.components, pubsubTopics); - } - - private async preparePushMessage( - encoder: IEncoder, - message: IMessage - ): Promise { - try { - if (!message.payload || message.payload.length === 0) { - log.error("Failed to send waku light push: payload is empty"); - return { query: null, error: ProtocolError.EMPTY_PAYLOAD }; - } - - if (!(await isMessageSizeUnderCap(encoder, message))) { - log.error("Failed to send waku light push: message is bigger than 1MB"); - return { query: null, error: ProtocolError.SIZE_TOO_BIG }; - } - - const protoMessage = await encoder.toProtoObj(message); - if (!protoMessage) { - log.error("Failed to encode to protoMessage, aborting push"); - return { - query: null, - error: ProtocolError.ENCODE_FAILED - }; - } - - const query = PushRpc.createRequest(protoMessage, encoder.pubsubTopic); - return { query, error: null }; - } catch (error) { - log.error("Failed to prepare push message", error); - - return { - query: null, - error: ProtocolError.GENERIC_FAIL - }; - } - } - - // TODO(weboko): use peer.id as parameter instead - public async send( - encoder: IEncoder, - message: IMessage, - peer: Peer - ): Promise { - const { query, error: preparationError } = await this.preparePushMessage( - encoder, - message - ); - - if (preparationError || !query) { - return { - success: null, - failure: { - error: preparationError, - peerId: peer.id - } - }; - } - - let stream: Stream; - try { - stream = await this.getStream(peer); - } catch (error) { - log.error("Failed to get stream", error); - return { - success: null, - failure: { - error: ProtocolError.NO_STREAM_AVAILABLE, - peerId: peer.id - } - }; - } - - let res: Uint8ArrayList[] | undefined; - try { - res = await pipe( - [query.encode()], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - } catch (err) { - log.error("Failed to send waku light push request", err); - return { - success: null, - failure: { - error: ProtocolError.GENERIC_FAIL, - peerId: peer.id - } - }; - } - - const bytes = new Uint8ArrayList(); - res.forEach((chunk) => { - bytes.append(chunk); - }); - - let response: PushResponse | undefined; - try { - response = PushRpc.decode(bytes).response; - } catch (err) { - log.error("Failed to decode push reply", err); - return { - success: null, - failure: { - error: ProtocolError.DECODE_FAILED, - peerId: peer.id - } - }; - } - - if (!response) { - log.error("Remote peer fault: No response in PushRPC"); - return { - success: null, - failure: { - error: ProtocolError.NO_RESPONSE, - peerId: peer.id - } - }; - } - - if (isRLNResponseError(response.info)) { - const rlnErrorCase = matchRLNErrorMessage(response.info!); - log.error("Remote peer rejected the message: ", rlnErrorCase); - return { - success: null, - failure: { - error: rlnErrorCase, - peerId: peer.id - } - }; - } - - if (!response.isSuccess) { - log.error("Remote peer rejected the message: ", response.info); - return { - success: null, - failure: { - error: ProtocolError.REMOTE_PEER_REJECTED, - peerId: peer.id - } - }; - } - - return { success: peer.id, failure: null }; - } -} +export { LightPushCore, LightPushCodec, PushResponse } from "./light_push.js"; diff --git a/packages/core/src/lib/light_push/light_push.ts b/packages/core/src/lib/light_push/light_push.ts new file mode 100644 index 0000000000..d649df7a21 --- /dev/null +++ b/packages/core/src/lib/light_push/light_push.ts @@ -0,0 +1,189 @@ +import type { Peer, Stream } from "@libp2p/interface"; +import { + type CoreProtocolResult, + type IBaseProtocolCore, + type IEncoder, + type IMessage, + type Libp2p, + ProtocolError, + PubsubTopic, + type ThisOrThat +} from "@waku/interfaces"; +import { PushResponse } from "@waku/proto"; +import { isMessageSizeUnderCap } from "@waku/utils"; +import { Logger } from "@waku/utils"; +import all from "it-all"; +import * as lp from "it-length-prefixed"; +import { pipe } from "it-pipe"; +import { Uint8ArrayList } from "uint8arraylist"; + +import { BaseProtocol } from "../base_protocol.js"; + +import { PushRpc } from "./push_rpc.js"; +import { isRLNResponseError, matchRLNErrorMessage } from "./utils.js"; + +const log = new Logger("light-push"); + +export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1"; +export { PushResponse }; + +type PreparePushMessageResult = ThisOrThat<"query", PushRpc>; + +/** + * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/). + */ +export class LightPushCore extends BaseProtocol implements IBaseProtocolCore { + public constructor( + public readonly pubsubTopics: PubsubTopic[], + libp2p: Libp2p + ) { + super(LightPushCodec, libp2p.components, pubsubTopics); + } + + private async preparePushMessage( + encoder: IEncoder, + message: IMessage + ): Promise { + try { + if (!message.payload || message.payload.length === 0) { + log.error("Failed to send waku light push: payload is empty"); + return { query: null, error: ProtocolError.EMPTY_PAYLOAD }; + } + + if (!(await isMessageSizeUnderCap(encoder, message))) { + log.error("Failed to send waku light push: message is bigger than 1MB"); + return { query: null, error: ProtocolError.SIZE_TOO_BIG }; + } + + const protoMessage = await encoder.toProtoObj(message); + if (!protoMessage) { + log.error("Failed to encode to protoMessage, aborting push"); + return { + query: null, + error: ProtocolError.ENCODE_FAILED + }; + } + + const query = PushRpc.createRequest(protoMessage, encoder.pubsubTopic); + return { query, error: null }; + } catch (error) { + log.error("Failed to prepare push message", error); + + return { + query: null, + error: ProtocolError.GENERIC_FAIL + }; + } + } + + // TODO(weboko): use peer.id as parameter instead + public async send( + encoder: IEncoder, + message: IMessage, + peer: Peer + ): Promise { + const { query, error: preparationError } = await this.preparePushMessage( + encoder, + message + ); + + if (preparationError || !query) { + return { + success: null, + failure: { + error: preparationError, + peerId: peer.id + } + }; + } + + let stream: Stream; + try { + stream = await this.getStream(peer); + } catch (error) { + log.error("Failed to get stream", error); + return { + success: null, + failure: { + error: ProtocolError.NO_STREAM_AVAILABLE, + peerId: peer.id + } + }; + } + + let res: Uint8ArrayList[] | undefined; + try { + res = await pipe( + [query.encode()], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + } catch (err) { + log.error("Failed to send waku light push request", err); + return { + success: null, + failure: { + error: ProtocolError.GENERIC_FAIL, + peerId: peer.id + } + }; + } + + const bytes = new Uint8ArrayList(); + res.forEach((chunk) => { + bytes.append(chunk); + }); + + let response: PushResponse | undefined; + try { + response = PushRpc.decode(bytes).response; + } catch (err) { + log.error("Failed to decode push reply", err); + return { + success: null, + failure: { + error: ProtocolError.DECODE_FAILED, + peerId: peer.id + } + }; + } + + if (!response) { + log.error("Remote peer fault: No response in PushRPC"); + return { + success: null, + failure: { + error: ProtocolError.NO_RESPONSE, + peerId: peer.id + } + }; + } + + if (isRLNResponseError(response.info)) { + const rlnErrorCase = matchRLNErrorMessage(response.info!); + log.error("Remote peer rejected the message: ", rlnErrorCase); + return { + success: null, + failure: { + error: rlnErrorCase, + peerId: peer.id + } + }; + } + + if (!response.isSuccess) { + log.error("Remote peer rejected the message: ", response.info); + return { + success: null, + failure: { + error: ProtocolError.REMOTE_PEER_REJECTED, + peerId: peer.id + } + }; + } + + return { success: peer.id, failure: null }; + } +} diff --git a/packages/core/src/lib/metadata/index.ts b/packages/core/src/lib/metadata/index.ts index 2b3bfe2532..d0cf0badc9 100644 --- a/packages/core/src/lib/metadata/index.ts +++ b/packages/core/src/lib/metadata/index.ts @@ -1,182 +1 @@ -import type { PeerId } from "@libp2p/interface"; -import { IncomingStreamData } from "@libp2p/interface"; -import { - type IMetadata, - type Libp2pComponents, - type MetadataQueryResult, - type PeerIdStr, - ProtocolError, - PubsubTopic, - type ShardInfo -} from "@waku/interfaces"; -import { proto_metadata } from "@waku/proto"; -import { encodeRelayShard, Logger, pubsubTopicsToShardInfo } from "@waku/utils"; -import all from "it-all"; -import * as lp from "it-length-prefixed"; -import { pipe } from "it-pipe"; -import { Uint8ArrayList } from "uint8arraylist"; - -import { BaseProtocol } from "../base_protocol.js"; - -const log = new Logger("metadata"); - -export const MetadataCodec = "/vac/waku/metadata/1.0.0"; - -class Metadata extends BaseProtocol implements IMetadata { - private libp2pComponents: Libp2pComponents; - protected handshakesConfirmed: Map = new Map(); - - public constructor( - public pubsubTopics: PubsubTopic[], - libp2p: Libp2pComponents - ) { - super(MetadataCodec, libp2p.components, pubsubTopics); - this.libp2pComponents = libp2p; - void libp2p.registrar.handle(MetadataCodec, (streamData) => { - void this.onRequest(streamData); - }); - } - - /** - * Make a metadata query to a peer - */ - public async query(peerId: PeerId): Promise { - const request = proto_metadata.WakuMetadataRequest.encode( - pubsubTopicsToShardInfo(this.pubsubTopics) - ); - - const peer = await this.libp2pComponents.peerStore.get(peerId); - if (!peer) { - return { - shardInfo: null, - error: ProtocolError.NO_PEER_AVAILABLE - }; - } - - let stream; - try { - stream = await this.getStream(peer); - } catch (error) { - log.error("Failed to get stream", error); - return { - shardInfo: null, - error: ProtocolError.NO_STREAM_AVAILABLE - }; - } - - const encodedResponse = await pipe( - [request], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - - const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse); - - if (error) { - return { - shardInfo: null, - error - }; - } - - await this.savePeerShardInfo(peerId, shardInfo); - - return { - shardInfo, - error: null - }; - } - - public async confirmOrAttemptHandshake( - peerId: PeerId - ): Promise { - const shardInfo = this.handshakesConfirmed.get(peerId.toString()); - if (shardInfo) { - return { - shardInfo, - error: null - }; - } - - return await this.query(peerId); - } - - /** - * Handle an incoming metadata request - */ - private async onRequest(streamData: IncomingStreamData): Promise { - try { - const { stream, connection } = streamData; - const encodedShardInfo = proto_metadata.WakuMetadataResponse.encode( - pubsubTopicsToShardInfo(this.pubsubTopics) - ); - - const encodedResponse = await pipe( - [encodedShardInfo], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - - const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse); - - if (error) { - return; - } - - await this.savePeerShardInfo(connection.remotePeer, shardInfo); - } catch (error) { - log.error("Error handling metadata request", error); - } - } - - private decodeMetadataResponse( - encodedResponse: Uint8ArrayList[] - ): MetadataQueryResult { - const bytes = new Uint8ArrayList(); - - encodedResponse.forEach((chunk) => { - bytes.append(chunk); - }); - const response = proto_metadata.WakuMetadataResponse.decode( - bytes - ) as ShardInfo; - - if (!response) { - log.error("Error decoding metadata response"); - return { - shardInfo: null, - error: ProtocolError.DECODE_FAILED - }; - } - - return { - shardInfo: response, - error: null - }; - } - - private async savePeerShardInfo( - peerId: PeerId, - shardInfo: ShardInfo - ): Promise { - // add or update the shardInfo to peer store - await this.libp2pComponents.peerStore.merge(peerId, { - metadata: { - shardInfo: encodeRelayShard(shardInfo) - } - }); - - this.handshakesConfirmed.set(peerId.toString(), shardInfo); - } -} - -export function wakuMetadata( - pubsubTopics: PubsubTopic[] -): (components: Libp2pComponents) => IMetadata { - return (components: Libp2pComponents) => - new Metadata(pubsubTopics, components); -} +export { wakuMetadata, MetadataCodec } from "./metadata.js"; diff --git a/packages/core/src/lib/metadata/metadata.ts b/packages/core/src/lib/metadata/metadata.ts new file mode 100644 index 0000000000..2b3bfe2532 --- /dev/null +++ b/packages/core/src/lib/metadata/metadata.ts @@ -0,0 +1,182 @@ +import type { PeerId } from "@libp2p/interface"; +import { IncomingStreamData } from "@libp2p/interface"; +import { + type IMetadata, + type Libp2pComponents, + type MetadataQueryResult, + type PeerIdStr, + ProtocolError, + PubsubTopic, + type ShardInfo +} from "@waku/interfaces"; +import { proto_metadata } from "@waku/proto"; +import { encodeRelayShard, Logger, pubsubTopicsToShardInfo } from "@waku/utils"; +import all from "it-all"; +import * as lp from "it-length-prefixed"; +import { pipe } from "it-pipe"; +import { Uint8ArrayList } from "uint8arraylist"; + +import { BaseProtocol } from "../base_protocol.js"; + +const log = new Logger("metadata"); + +export const MetadataCodec = "/vac/waku/metadata/1.0.0"; + +class Metadata extends BaseProtocol implements IMetadata { + private libp2pComponents: Libp2pComponents; + protected handshakesConfirmed: Map = new Map(); + + public constructor( + public pubsubTopics: PubsubTopic[], + libp2p: Libp2pComponents + ) { + super(MetadataCodec, libp2p.components, pubsubTopics); + this.libp2pComponents = libp2p; + void libp2p.registrar.handle(MetadataCodec, (streamData) => { + void this.onRequest(streamData); + }); + } + + /** + * Make a metadata query to a peer + */ + public async query(peerId: PeerId): Promise { + const request = proto_metadata.WakuMetadataRequest.encode( + pubsubTopicsToShardInfo(this.pubsubTopics) + ); + + const peer = await this.libp2pComponents.peerStore.get(peerId); + if (!peer) { + return { + shardInfo: null, + error: ProtocolError.NO_PEER_AVAILABLE + }; + } + + let stream; + try { + stream = await this.getStream(peer); + } catch (error) { + log.error("Failed to get stream", error); + return { + shardInfo: null, + error: ProtocolError.NO_STREAM_AVAILABLE + }; + } + + const encodedResponse = await pipe( + [request], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + + const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse); + + if (error) { + return { + shardInfo: null, + error + }; + } + + await this.savePeerShardInfo(peerId, shardInfo); + + return { + shardInfo, + error: null + }; + } + + public async confirmOrAttemptHandshake( + peerId: PeerId + ): Promise { + const shardInfo = this.handshakesConfirmed.get(peerId.toString()); + if (shardInfo) { + return { + shardInfo, + error: null + }; + } + + return await this.query(peerId); + } + + /** + * Handle an incoming metadata request + */ + private async onRequest(streamData: IncomingStreamData): Promise { + try { + const { stream, connection } = streamData; + const encodedShardInfo = proto_metadata.WakuMetadataResponse.encode( + pubsubTopicsToShardInfo(this.pubsubTopics) + ); + + const encodedResponse = await pipe( + [encodedShardInfo], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + + const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse); + + if (error) { + return; + } + + await this.savePeerShardInfo(connection.remotePeer, shardInfo); + } catch (error) { + log.error("Error handling metadata request", error); + } + } + + private decodeMetadataResponse( + encodedResponse: Uint8ArrayList[] + ): MetadataQueryResult { + const bytes = new Uint8ArrayList(); + + encodedResponse.forEach((chunk) => { + bytes.append(chunk); + }); + const response = proto_metadata.WakuMetadataResponse.decode( + bytes + ) as ShardInfo; + + if (!response) { + log.error("Error decoding metadata response"); + return { + shardInfo: null, + error: ProtocolError.DECODE_FAILED + }; + } + + return { + shardInfo: response, + error: null + }; + } + + private async savePeerShardInfo( + peerId: PeerId, + shardInfo: ShardInfo + ): Promise { + // add or update the shardInfo to peer store + await this.libp2pComponents.peerStore.merge(peerId, { + metadata: { + shardInfo: encodeRelayShard(shardInfo) + } + }); + + this.handshakesConfirmed.set(peerId.toString(), shardInfo); + } +} + +export function wakuMetadata( + pubsubTopics: PubsubTopic[] +): (components: Libp2pComponents) => IMetadata { + return (components: Libp2pComponents) => + new Metadata(pubsubTopics, components); +} diff --git a/packages/core/src/lib/store/index.ts b/packages/core/src/lib/store/index.ts index 830a7b5100..bc1d7727d7 100644 --- a/packages/core/src/lib/store/index.ts +++ b/packages/core/src/lib/store/index.ts @@ -1,136 +1 @@ -import type { Peer } from "@libp2p/interface"; -import { - IDecodedMessage, - IDecoder, - IStoreCore, - Libp2p, - PubsubTopic, - QueryRequestParams -} from "@waku/interfaces"; -import { Logger } from "@waku/utils"; -import all from "it-all"; -import * as lp from "it-length-prefixed"; -import { pipe } from "it-pipe"; -import { Uint8ArrayList } from "uint8arraylist"; - -import { BaseProtocol } from "../base_protocol.js"; -import { toProtoMessage } from "../to_proto_message.js"; - -import { - DEFAULT_PAGE_SIZE, - MAX_PAGE_SIZE, - StoreQueryRequest, - StoreQueryResponse -} from "./rpc.js"; - -const log = new Logger("store"); - -export const StoreCodec = "/vac/waku/store-query/3.0.0"; - -export class StoreCore extends BaseProtocol implements IStoreCore { - public constructor( - public readonly pubsubTopics: PubsubTopic[], - libp2p: Libp2p - ) { - super(StoreCodec, libp2p.components, pubsubTopics); - } - - public async *queryPerPage( - queryOpts: QueryRequestParams, - decoders: Map>, - peer: Peer - ): AsyncGenerator[]> { - if ( - queryOpts.contentTopics.toString() !== - Array.from(decoders.keys()).toString() - ) { - throw new Error( - "Internal error, the decoders should match the query's content topics" - ); - } - - let currentCursor = queryOpts.paginationCursor; - while (true) { - const storeQueryRequest = StoreQueryRequest.create({ - ...queryOpts, - paginationCursor: currentCursor - }); - - let stream; - try { - stream = await this.getStream(peer); - } catch (e) { - log.error("Failed to get stream", e); - break; - } - - const res = await pipe( - [storeQueryRequest.encode()], - lp.encode, - stream, - lp.decode, - async (source) => await all(source) - ); - - const bytes = new Uint8ArrayList(); - res.forEach((chunk) => { - bytes.append(chunk); - }); - - const storeQueryResponse = StoreQueryResponse.decode(bytes); - - if ( - !storeQueryResponse.statusCode || - storeQueryResponse.statusCode >= 300 - ) { - const errorMessage = `Store query failed with status code: ${storeQueryResponse.statusCode}, description: ${storeQueryResponse.statusDesc}`; - log.error(errorMessage); - throw new Error(errorMessage); - } - - if (!storeQueryResponse.messages || !storeQueryResponse.messages.length) { - log.warn("Stopping pagination due to empty messages in response"); - break; - } - - log.info( - `${storeQueryResponse.messages.length} messages retrieved from store` - ); - - const decodedMessages = storeQueryResponse.messages.map((protoMsg) => { - if (!protoMsg.message) { - return Promise.resolve(undefined); - } - const contentTopic = protoMsg.message.contentTopic; - if (contentTopic) { - const decoder = decoders.get(contentTopic); - if (decoder) { - return decoder.fromProtoObj( - protoMsg.pubsubTopic || "", - toProtoMessage(protoMsg.message) - ); - } - } - return Promise.resolve(undefined); - }); - - yield decodedMessages; - - if (queryOpts.paginationForward) { - currentCursor = - storeQueryResponse.messages[storeQueryResponse.messages.length - 1] - .messageHash; - } else { - currentCursor = storeQueryResponse.messages[0].messageHash; - } - - if ( - storeQueryResponse.messages.length > MAX_PAGE_SIZE && - storeQueryResponse.messages.length < - (queryOpts.paginationLimit || DEFAULT_PAGE_SIZE) - ) { - break; - } - } - } -} +export { StoreCore, StoreCodec } from "./store.js"; diff --git a/packages/core/src/lib/store/store.ts b/packages/core/src/lib/store/store.ts new file mode 100644 index 0000000000..830a7b5100 --- /dev/null +++ b/packages/core/src/lib/store/store.ts @@ -0,0 +1,136 @@ +import type { Peer } from "@libp2p/interface"; +import { + IDecodedMessage, + IDecoder, + IStoreCore, + Libp2p, + PubsubTopic, + QueryRequestParams +} from "@waku/interfaces"; +import { Logger } from "@waku/utils"; +import all from "it-all"; +import * as lp from "it-length-prefixed"; +import { pipe } from "it-pipe"; +import { Uint8ArrayList } from "uint8arraylist"; + +import { BaseProtocol } from "../base_protocol.js"; +import { toProtoMessage } from "../to_proto_message.js"; + +import { + DEFAULT_PAGE_SIZE, + MAX_PAGE_SIZE, + StoreQueryRequest, + StoreQueryResponse +} from "./rpc.js"; + +const log = new Logger("store"); + +export const StoreCodec = "/vac/waku/store-query/3.0.0"; + +export class StoreCore extends BaseProtocol implements IStoreCore { + public constructor( + public readonly pubsubTopics: PubsubTopic[], + libp2p: Libp2p + ) { + super(StoreCodec, libp2p.components, pubsubTopics); + } + + public async *queryPerPage( + queryOpts: QueryRequestParams, + decoders: Map>, + peer: Peer + ): AsyncGenerator[]> { + if ( + queryOpts.contentTopics.toString() !== + Array.from(decoders.keys()).toString() + ) { + throw new Error( + "Internal error, the decoders should match the query's content topics" + ); + } + + let currentCursor = queryOpts.paginationCursor; + while (true) { + const storeQueryRequest = StoreQueryRequest.create({ + ...queryOpts, + paginationCursor: currentCursor + }); + + let stream; + try { + stream = await this.getStream(peer); + } catch (e) { + log.error("Failed to get stream", e); + break; + } + + const res = await pipe( + [storeQueryRequest.encode()], + lp.encode, + stream, + lp.decode, + async (source) => await all(source) + ); + + const bytes = new Uint8ArrayList(); + res.forEach((chunk) => { + bytes.append(chunk); + }); + + const storeQueryResponse = StoreQueryResponse.decode(bytes); + + if ( + !storeQueryResponse.statusCode || + storeQueryResponse.statusCode >= 300 + ) { + const errorMessage = `Store query failed with status code: ${storeQueryResponse.statusCode}, description: ${storeQueryResponse.statusDesc}`; + log.error(errorMessage); + throw new Error(errorMessage); + } + + if (!storeQueryResponse.messages || !storeQueryResponse.messages.length) { + log.warn("Stopping pagination due to empty messages in response"); + break; + } + + log.info( + `${storeQueryResponse.messages.length} messages retrieved from store` + ); + + const decodedMessages = storeQueryResponse.messages.map((protoMsg) => { + if (!protoMsg.message) { + return Promise.resolve(undefined); + } + const contentTopic = protoMsg.message.contentTopic; + if (contentTopic) { + const decoder = decoders.get(contentTopic); + if (decoder) { + return decoder.fromProtoObj( + protoMsg.pubsubTopic || "", + toProtoMessage(protoMsg.message) + ); + } + } + return Promise.resolve(undefined); + }); + + yield decodedMessages; + + if (queryOpts.paginationForward) { + currentCursor = + storeQueryResponse.messages[storeQueryResponse.messages.length - 1] + .messageHash; + } else { + currentCursor = storeQueryResponse.messages[0].messageHash; + } + + if ( + storeQueryResponse.messages.length > MAX_PAGE_SIZE && + storeQueryResponse.messages.length < + (queryOpts.paginationLimit || DEFAULT_PAGE_SIZE) + ) { + break; + } + } + } +} diff --git a/packages/sdk/src/protocols/filter/constants.ts b/packages/sdk/src/filter/constants.ts similarity index 100% rename from packages/sdk/src/protocols/filter/constants.ts rename to packages/sdk/src/filter/constants.ts diff --git a/packages/sdk/src/protocols/filter/filter.ts b/packages/sdk/src/filter/filter.ts similarity index 100% rename from packages/sdk/src/protocols/filter/filter.ts rename to packages/sdk/src/filter/filter.ts diff --git a/packages/sdk/src/protocols/filter/index.ts b/packages/sdk/src/filter/index.ts similarity index 100% rename from packages/sdk/src/protocols/filter/index.ts rename to packages/sdk/src/filter/index.ts diff --git a/packages/sdk/src/protocols/filter/subscription.ts b/packages/sdk/src/filter/subscription.ts similarity index 100% rename from packages/sdk/src/protocols/filter/subscription.ts rename to packages/sdk/src/filter/subscription.ts diff --git a/packages/sdk/src/protocols/filter/subscription_monitor.ts b/packages/sdk/src/filter/subscription_monitor.ts similarity index 100% rename from packages/sdk/src/protocols/filter/subscription_monitor.ts rename to packages/sdk/src/filter/subscription_monitor.ts diff --git a/packages/sdk/src/protocols/filter/utils.ts b/packages/sdk/src/filter/utils.ts similarity index 100% rename from packages/sdk/src/protocols/filter/utils.ts rename to packages/sdk/src/filter/utils.ts diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 4bf636e6b1..9df8d47c45 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -14,9 +14,9 @@ export { defaultLibp2p, createLibp2pAndUpdateOptions } from "./create/index.js"; -export { wakuLightPush } from "./protocols/light_push/index.js"; -export { wakuFilter } from "./protocols/filter/index.js"; -export { wakuStore } from "./protocols/store/index.js"; +export { wakuLightPush } from "./light_push/index.js"; +export { wakuFilter } from "./filter/index.js"; +export { wakuStore } from "./store/index.js"; export * as waku from "@waku/core"; export * as utils from "@waku/utils"; diff --git a/packages/sdk/src/protocols/light_push/index.ts b/packages/sdk/src/light_push/index.ts similarity index 100% rename from packages/sdk/src/protocols/light_push/index.ts rename to packages/sdk/src/light_push/index.ts diff --git a/packages/sdk/src/protocols/light_push/light_push.spec.ts b/packages/sdk/src/light_push/light_push.spec.ts similarity index 100% rename from packages/sdk/src/protocols/light_push/light_push.spec.ts rename to packages/sdk/src/light_push/light_push.spec.ts diff --git a/packages/sdk/src/protocols/light_push/light_push.ts b/packages/sdk/src/light_push/light_push.ts similarity index 100% rename from packages/sdk/src/protocols/light_push/light_push.ts rename to packages/sdk/src/light_push/light_push.ts diff --git a/packages/sdk/src/protocols/peer_manager/index.ts b/packages/sdk/src/peer_manager/index.ts similarity index 100% rename from packages/sdk/src/protocols/peer_manager/index.ts rename to packages/sdk/src/peer_manager/index.ts diff --git a/packages/sdk/src/protocols/peer_manager/peer_manager.spec.ts b/packages/sdk/src/peer_manager/peer_manager.spec.ts similarity index 100% rename from packages/sdk/src/protocols/peer_manager/peer_manager.spec.ts rename to packages/sdk/src/peer_manager/peer_manager.spec.ts diff --git a/packages/sdk/src/protocols/peer_manager/peer_manager.ts b/packages/sdk/src/peer_manager/peer_manager.ts similarity index 100% rename from packages/sdk/src/protocols/peer_manager/peer_manager.ts rename to packages/sdk/src/peer_manager/peer_manager.ts diff --git a/packages/sdk/src/protocols/store/index.ts b/packages/sdk/src/store/index.ts similarity index 100% rename from packages/sdk/src/protocols/store/index.ts rename to packages/sdk/src/store/index.ts diff --git a/packages/sdk/src/protocols/store/store.ts b/packages/sdk/src/store/store.ts similarity index 100% rename from packages/sdk/src/protocols/store/store.ts rename to packages/sdk/src/store/store.ts diff --git a/packages/sdk/src/waku/waku.ts b/packages/sdk/src/waku/waku.ts index 8d1800dedb..d057b758db 100644 --- a/packages/sdk/src/waku/waku.ts +++ b/packages/sdk/src/waku/waku.ts @@ -16,10 +16,10 @@ import type { import { Protocols } from "@waku/interfaces"; import { Logger } from "@waku/utils"; -import { wakuFilter } from "../protocols/filter/index.js"; -import { wakuLightPush } from "../protocols/light_push/index.js"; -import { PeerManager } from "../protocols/peer_manager/index.js"; -import { wakuStore } from "../protocols/store/index.js"; +import { wakuFilter } from "../filter/index.js"; +import { wakuLightPush } from "../light_push/index.js"; +import { PeerManager } from "../peer_manager/index.js"; +import { wakuStore } from "../store/index.js"; import { waitForRemotePeer } from "./wait_for_remote_peer.js"; From da1ffe636d143351abe5fb88fa68abd9160e2993 Mon Sep 17 00:00:00 2001 From: Sasha Date: Thu, 30 Jan 2025 23:53:44 +0100 Subject: [PATCH 22/22] remove only --- packages/sdk/src/peer_manager/peer_manager.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/peer_manager/peer_manager.spec.ts b/packages/sdk/src/peer_manager/peer_manager.spec.ts index 358f55489e..2930d90405 100644 --- a/packages/sdk/src/peer_manager/peer_manager.spec.ts +++ b/packages/sdk/src/peer_manager/peer_manager.spec.ts @@ -5,7 +5,7 @@ import sinon from "sinon"; import { PeerManager } from "./peer_manager.js"; -describe.only("PeerManager", () => { +describe("PeerManager", () => { let libp2p: Libp2p; let peerManager: PeerManager;