From 34aa651dfd62660706c84f277c134b40234d366e Mon Sep 17 00:00:00 2001 From: rozaso Date: Fri, 17 Jan 2025 10:57:54 -0800 Subject: [PATCH 01/10] Initial Implementation of WebSockets in VNodes. --- docker/a.yml | 84 +- docker/debug-v1.sh | 29 + docker/debug-v2.sh | 29 + docker/debug-v3.sh | 29 + docker/evm.yml | 6 +- docker/s.yml | 80 +- docker/v.yml | 120 +- docker/v1/.env | 7 + docker/v10/.env | 6 + docker/v2/.env | 6 + docker/v3/.env | 6 + docker/v4/.env | 6 + docker/v5/.env | 6 + docker/v6/.env | 6 + docker/v7/.env | 6 + docker/v8/.env | 6 + docker/v9/.env | 6 + src/app.ts | 17 + src/services/WebSockets/BlockStatusManager.ts | 57 + src/services/WebSockets/DiscoverService.ts | 231 ++++ src/services/WebSockets/WebSocketClient.ts | 412 ++++++ src/services/WebSockets/WebSocketManager.ts | 75 ++ src/services/WebSockets/WebSocketServer.ts | 132 ++ src/services/WebSockets/types.ts | 48 + src/utilz/envLoader.ts | 5 + tsconfig.json | 2 +- yarn.lock | 1173 +++++++++-------- 27 files changed, 1925 insertions(+), 665 deletions(-) create mode 100644 docker/debug-v1.sh create mode 100644 docker/debug-v2.sh create mode 100644 docker/debug-v3.sh create mode 100644 src/services/WebSockets/BlockStatusManager.ts create mode 100644 src/services/WebSockets/DiscoverService.ts create mode 100644 src/services/WebSockets/WebSocketClient.ts create mode 100644 src/services/WebSockets/WebSocketManager.ts create mode 100644 src/services/WebSockets/WebSocketServer.ts create mode 100644 src/services/WebSockets/types.ts diff --git a/docker/a.yml b/docker/a.yml index 21e2836..ddefa12 100644 --- a/docker/a.yml +++ b/docker/a.yml @@ -24,50 +24,50 @@ services: - ./a1/log:/log - ./_abi/:/config/abi/ - anode2: - image: anode-main - container_name: anode2 - networks: - push-dev-network: - aliases: - - anode2.local - environment: - DB_NAME: anode2 - PORT: 5002 - env_file: - - .env - - common.env - - a-specific.env - ports: - - "5002:5002" - entrypoint: ['sh', '/entrypoint.sh'] - volumes: - - ./a2:/config - - ./a2/log:/log - - ./_abi/:/config/abi/ + # anode2: + # image: anode-main + # container_name: anode2 + # networks: + # push-dev-network: + # aliases: + # - anode2.local + # environment: + # DB_NAME: anode2 + # PORT: 5002 + # env_file: + # - .env + # - common.env + # - a-specific.env + # ports: + # - "5002:5002" + # entrypoint: ['sh', '/entrypoint.sh'] + # volumes: + # - ./a2:/config + # - ./a2/log:/log + # - ./_abi/:/config/abi/ - anode3: - image: anode-main - container_name: anode3 - networks: - push-dev-network: - aliases: - - anode3.local - environment: - DB_NAME: anode3 - PORT: 5003 - env_file: - - .env - - common.env - - a-specific.env - ports: - - "5003:5003" - entrypoint: ['sh', '/entrypoint.sh'] - volumes: - - ./a3:/config - - ./a3/log:/log - - ./_abi/:/config/abi/ + # anode3: + # image: anode-main + # container_name: anode3 + # networks: + # push-dev-network: + # aliases: + # - anode3.local + # environment: + # DB_NAME: anode3 + # PORT: 5003 + # env_file: + # - .env + # - common.env + # - a-specific.env + # ports: + # - "5003:5003" + # entrypoint: ['sh', '/entrypoint.sh'] + # volumes: + # - ./a3:/config + # - ./a3/log:/log + # - ./_abi/:/config/abi/ diff --git a/docker/debug-v1.sh b/docker/debug-v1.sh new file mode 100644 index 0000000..e925d1c --- /dev/null +++ b/docker/debug-v1.sh @@ -0,0 +1,29 @@ +CHAIN_DIR="/Users/apasha/projects/push" +DOC_DIR="/Users/apasha/projects/push/push-vnode/docker" + + cd ${DOC_DIR} + set -o allexport + source ${DOC_DIR}/.env + source ${DOC_DIR}/common.env + source ${DOC_DIR}/v-specific.env + set +o allexport + + export CONFIG_DIR=${DOC_DIR}/v1 + export LOG_DIR=${CONFIG_DIR}/log + export ABI_DIR=${DOC_DIR}/_abi + export ETH_KEY_PATH=${CONFIG_DIR}/node_key.json + export LOCALH=true + export VALIDATOR_PING_SCHEDULE="* */30 * * * *" + + export PG_HOST=localhost + export PG_PORT=${EXTERNAL_PG_PORT} + export DB_NAME=vnode1 + export PORT=4001 + export REDIS_URL=redis://localhost:${EXTERNAL_REDIS_PORT} + export VALIDATOR_RPC_ENDPOINT=http://localhost:8545 + + echo > ${LOG_DIR}/debug.log + echo > ${LOG_DIR}/error.log + cd ${DOC_DIR}/.. + yarn start + \ No newline at end of file diff --git a/docker/debug-v2.sh b/docker/debug-v2.sh new file mode 100644 index 0000000..b32f95c --- /dev/null +++ b/docker/debug-v2.sh @@ -0,0 +1,29 @@ +CHAIN_DIR="/Users/apasha/projects/push" +DOC_DIR="/Users/apasha/projects/push/push-vnode/docker" + + cd ${DOC_DIR} + set -o allexport + source ${DOC_DIR}/.env + source ${DOC_DIR}/common.env + source ${DOC_DIR}/v-specific.env + set +o allexport + + export CONFIG_DIR=${DOC_DIR}/v2 + export LOG_DIR=${CONFIG_DIR}/log + export ABI_DIR=${DOC_DIR}/_abi + export ETH_KEY_PATH=${CONFIG_DIR}/node_key.json + export LOCALH=true + export VALIDATOR_PING_SCHEDULE="* */30 * * * *" + + export PG_HOST=localhost + export PG_PORT=${EXTERNAL_PG_PORT} + export DB_NAME=vnode2 + export PORT=4002 + export REDIS_URL=redis://localhost:${EXTERNAL_REDIS_PORT} + export VALIDATOR_RPC_ENDPOINT=http://localhost:8545 + + echo > ${LOG_DIR}/debug.log + echo > ${LOG_DIR}/error.log + cd ${DOC_DIR}/.. + yarn dev + \ No newline at end of file diff --git a/docker/debug-v3.sh b/docker/debug-v3.sh new file mode 100644 index 0000000..208dc26 --- /dev/null +++ b/docker/debug-v3.sh @@ -0,0 +1,29 @@ +CHAIN_DIR="/Users/apasha/projects/push" +DOC_DIR="/Users/apasha/projects/push/push-vnode/docker" + + cd ${DOC_DIR} + set -o allexport + source ${DOC_DIR}/.env + source ${DOC_DIR}/common.env + source ${DOC_DIR}/v-specific.env + set +o allexport + + export CONFIG_DIR=${DOC_DIR}/v3 + export LOG_DIR=${CONFIG_DIR}/log + export ABI_DIR=${DOC_DIR}/_abi + export ETH_KEY_PATH=${CONFIG_DIR}/node_key.json + export LOCALH=true + export VALIDATOR_PING_SCHEDULE="* */30 * * * *" + + export PG_HOST=localhost + export PG_PORT=${EXTERNAL_PG_PORT} + export DB_NAME=vnode3 + export PORT=4003 + export REDIS_URL=redis://localhost:${EXTERNAL_REDIS_PORT} + export VALIDATOR_RPC_ENDPOINT=http://localhost:8545 + + echo > ${LOG_DIR}/debug.log + echo > ${LOG_DIR}/error.log + cd ${DOC_DIR}/.. + yarn start + \ No newline at end of file diff --git a/docker/evm.yml b/docker/evm.yml index 15e2339..47d346c 100644 --- a/docker/evm.yml +++ b/docker/evm.yml @@ -18,9 +18,9 @@ services: # remove everything: ./cleanup.sh # uncomment N number of records in v.yml ; s.yml; a.yml which matches the values above # re-create everything: ./setup.sh - VNODE_COUNT: 6 # max 10 - SNODE_COUNT: 3 # max 8 - ANODE_COUNT: 3 # max 5 + VNODE_COUNT: 3 # max 10 + SNODE_COUNT: 1 # max 8 + ANODE_COUNT: 1 # max 5 networks: diff --git a/docker/s.yml b/docker/s.yml index 22ff160..7be268a 100644 --- a/docker/s.yml +++ b/docker/s.yml @@ -24,48 +24,48 @@ services: - ./_abi/:/config/abi/ - snode2: - image: snode-main - container_name: snode2 - networks: - push-dev-network: - aliases: - - snode2.local - environment: - DB_NAME: snode2 - PORT: 3002 - env_file: - - .env - - common.env - - s-specific.env - ports: - - "3002:3002" - volumes: - - ./s2:/config - - ./s2/log:/log - - ./_abi/:/config/abi/ + # snode2: + # image: snode-main + # container_name: snode2 + # networks: + # push-dev-network: + # aliases: + # - snode2.local + # environment: + # DB_NAME: snode2 + # PORT: 3002 + # env_file: + # - .env + # - common.env + # - s-specific.env + # ports: + # - "3002:3002" + # volumes: + # - ./s2:/config + # - ./s2/log:/log + # - ./_abi/:/config/abi/ - snode3: - image: snode-main - container_name: snode3 - networks: - push-dev-network: - aliases: - - snode3.local - environment: - DB_NAME: snode3 - PORT: 3003 - env_file: - - .env - - common.env - - s-specific.env - ports: - - "3003:3003" - volumes: - - ./s3:/config - - ./s3/log:/log - - ./_abi/:/config/abi/ + # snode3: + # image: snode-main + # container_name: snode3 + # networks: + # push-dev-network: + # aliases: + # - snode3.local + # environment: + # DB_NAME: snode3 + # PORT: 3003 + # env_file: + # - .env + # - common.env + # - s-specific.env + # ports: + # - "3003:3003" + # volumes: + # - ./s3:/config + # - ./s3/log:/log + # - ./_abi/:/config/abi/ # snode4: diff --git a/docker/v.yml b/docker/v.yml index ae370c8..ad563f2 100644 --- a/docker/v.yml +++ b/docker/v.yml @@ -67,68 +67,68 @@ services: - ./_abi/:/config/abi/ - vnode4: - image: vnode-main - container_name: vnode4 - networks: - push-dev-network: - aliases: - - vnode4.local - environment: - DB_NAME: vnode4 - PORT: 4004 - env_file: - - .env - - common.env - - v-specific.env - ports: - - "4004:4004" - volumes: - - ./v4:/config - - ./v4/log:/log - - ./_abi/:/config/abi/ + # vnode4: + # image: vnode-main + # container_name: vnode4 + # networks: + # push-dev-network: + # aliases: + # - vnode4.local + # environment: + # DB_NAME: vnode4 + # PORT: 4004 + # env_file: + # - .env + # - common.env + # - v-specific.env + # ports: + # - "4004:4004" + # volumes: + # - ./v4:/config + # - ./v4/log:/log + # - ./_abi/:/config/abi/ - vnode5: - image: vnode-main - container_name: vnode5 - networks: - push-dev-network: - aliases: - - vnode5.local - environment: - DB_NAME: vnode5 - PORT: 4005 - env_file: - - .env - - common.env - - v-specific.env - ports: - - "4005:4005" - volumes: - - ./v5:/config - - ./v5/log:/log - - ./_abi/:/config/abi/ + # vnode5: + # image: vnode-main + # container_name: vnode5 + # networks: + # push-dev-network: + # aliases: + # - vnode5.local + # environment: + # DB_NAME: vnode5 + # PORT: 4005 + # env_file: + # - .env + # - common.env + # - v-specific.env + # ports: + # - "4005:4005" + # volumes: + # - ./v5:/config + # - ./v5/log:/log + # - ./_abi/:/config/abi/ - vnode6: - image: vnode-main - container_name: vnode6 - networks: - push-dev-network: - aliases: - - vnode6.local - environment: - DB_NAME: vnode6 - PORT: 4006 - env_file: - - .env - - common.env - - v-specific.env - ports: - - "4006:4006" - volumes: - - ./v6:/config - - ./v6/log:/log - - ./_abi/:/config/abi/ + # vnode6: + # image: vnode-main + # container_name: vnode6 + # networks: + # push-dev-network: + # aliases: + # - vnode6.local + # environment: + # DB_NAME: vnode6 + # PORT: 4006 + # env_file: + # - .env + # - common.env + # - v-specific.env + # ports: + # - "4006:4006" + # volumes: + # - ./v6:/config + # - ./v6/log:/log + # - ./_abi/:/config/abi/ # vnode7: # image: vnode-main diff --git a/docker/v1/.env b/docker/v1/.env index e69de29..69161bc 100644 --- a/docker/v1/.env +++ b/docker/v1/.env @@ -0,0 +1,7 @@ +WS_PORT=8080 +WS_MAX_PAYLOAD=5242880 + +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v10/.env b/docker/v10/.env index e69de29..a82a393 100644 --- a/docker/v10/.env +++ b/docker/v10/.env @@ -0,0 +1,6 @@ +WS_PORT=8090 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v2/.env b/docker/v2/.env index e69de29..073d595 100644 --- a/docker/v2/.env +++ b/docker/v2/.env @@ -0,0 +1,6 @@ +WS_PORT=8081 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v3/.env b/docker/v3/.env index e69de29..a4edc77 100644 --- a/docker/v3/.env +++ b/docker/v3/.env @@ -0,0 +1,6 @@ +WS_PORT=8083 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v4/.env b/docker/v4/.env index e69de29..0e6b675 100644 --- a/docker/v4/.env +++ b/docker/v4/.env @@ -0,0 +1,6 @@ +WS_PORT=8084 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v5/.env b/docker/v5/.env index e69de29..0eaa382 100644 --- a/docker/v5/.env +++ b/docker/v5/.env @@ -0,0 +1,6 @@ +WS_PORT=8085 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v6/.env b/docker/v6/.env index e69de29..ec9c9db 100644 --- a/docker/v6/.env +++ b/docker/v6/.env @@ -0,0 +1,6 @@ +WS_PORT=8086 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v7/.env b/docker/v7/.env index e69de29..b4d070d 100644 --- a/docker/v7/.env +++ b/docker/v7/.env @@ -0,0 +1,6 @@ +WS_PORT=8087 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v8/.env b/docker/v8/.env index e69de29..ab8e23a 100644 --- a/docker/v8/.env +++ b/docker/v8/.env @@ -0,0 +1,6 @@ +WS_PORT=8088 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v9/.env b/docker/v9/.env index e69de29..f1f59de 100644 --- a/docker/v9/.env +++ b/docker/v9/.env @@ -0,0 +1,6 @@ +WS_PORT=8089 +WS_MAX_PAYLOAD=5242880 +DISCOVERY_MIN_ARCHIVE_NODES=1 +DISCOVERY_MAX_CONNECTION_RETRIES=3 +DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 32ef409..6abab47 100644 --- a/src/app.ts +++ b/src/app.ts @@ -27,6 +27,8 @@ import winston from "winston"; import pgPromise from 'pg-promise' import { IClient } from 'pg-promise/typescript/pg-subset' import {DbLoader} from "./services/messaging/dbLoader"; +import { WebSocketManager } from "./services/WebSockets/WebSocketManager"; +import { ValidatorContractState } from "./services/messaging-common/validatorContractState"; let server: Server; let log = WinstonUtil.newLog("SERVER"); @@ -114,6 +116,21 @@ export async function initValidator() { const validatorNode = Container.get(ValidatorNode); await validatorNode.postConstruct(); + const validatorContractState = Container.get(ValidatorContractState); + await validatorContractState.postConstruct(); + const archivalNodes = validatorContractState.getArchivalNodesMap(); + + // Initialize WebSocket Manager with error handling + try { + const wsManager = Container.get(WebSocketManager); + await wsManager.postConstruct(validatorNode.nodeId, validatorContractState.wallet, archivalNodes); + log.info('WebSocket Manager initialized successfully'); + } catch (error) { + log.error('Failed to initialize WebSocket Manager:', error); + log.warn('Continuing with HTTP server initialization despite WebSocket failure'); + } + + const validatorRpc = Container.get(ValidatorRpc); Check.notNull(validatorRpc, 'ValidatorRpc is null'); diff --git a/src/services/WebSockets/BlockStatusManager.ts b/src/services/WebSockets/BlockStatusManager.ts new file mode 100644 index 0000000..62c67e8 --- /dev/null +++ b/src/services/WebSockets/BlockStatusManager.ts @@ -0,0 +1,57 @@ +import { Service } from 'typedi'; +import { WinstonUtil } from "../../utilz/winstonUtil"; +import { BlockData } from './types'; +import { WebSocketServer } from './WebSocketServer'; + +interface BlockConfirmation { + timestamp: number; + nodes: Set; +} + +@Service() +export class BlockStatusManager { + private confirmations = new Map(); + private readonly REQUIRED_CONFIRMATIONS = 2; + private readonly CONFIRMATION_EXPIRY_TIME = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + private readonly log = WinstonUtil.newLog("BlockStatusManager"); + + constructor(private readonly wsServer: WebSocketServer) {} + + async handleBlockConfirmation(blockHash: string, nodeId: string, blockData: BlockData) { + let confirmation = this.confirmations.get(blockHash); + if (!confirmation) { + confirmation = { + timestamp: Date.now(), + nodes: new Set() + }; + this.confirmations.set(blockHash, confirmation); + } + + confirmation.nodes.add(nodeId); + this.log.debug(`Block ${blockHash} confirmed by ${nodeId}. Total confirmations: ${confirmation.nodes.size}`); + + if (confirmation.nodes.size >= this.REQUIRED_CONFIRMATIONS) { + await this.handleBlockConfirmed(blockData); + } + } + + private async handleBlockConfirmed(blockData: BlockData) { + this.log.info(`Block ${blockData.hash} reached required confirmations`); + this.wsServer.broadcastBlockUpdate(blockData); + this.confirmations.delete(blockData.hash); + } + + private cleanupOldConfirmations() { + const now = Date.now(); + for (const [blockHash, confirmation] of this.confirmations.entries()) { + if (now - confirmation.timestamp > this.CONFIRMATION_EXPIRY_TIME) { + this.confirmations.delete(blockHash); + this.log.debug(`Cleaned up old confirmation for block ${blockHash}`); + } + } + } + + public performCleanup() { + this.cleanupOldConfirmations(); + } +} \ No newline at end of file diff --git a/src/services/WebSockets/DiscoverService.ts b/src/services/WebSockets/DiscoverService.ts new file mode 100644 index 0000000..a1df871 --- /dev/null +++ b/src/services/WebSockets/DiscoverService.ts @@ -0,0 +1,231 @@ +// src/services/DiscoveryService.ts +import WebSocket from 'ws'; +import { ArchiveNodeInfo, DiscoveryConfig } from './types'; +import { EnvLoader } from "../../utilz/envLoader"; +import { NodeInfo } from '../messaging-common/validatorContractState'; +import { WinstonUtil } from '../../utilz/winstonUtil'; + +export class DiscoveryService { + private archiveNodes = new Map(); + private refreshInterval: NodeJS.Timer; + private vNodeId: string; + private archivalNodes: Map; + private readonly log = WinstonUtil.newLog("DiscoveryService"); + + constructor( + private readonly config: DiscoveryConfig + ) { + this.config = { + refreshInterval: EnvLoader.getPropertyAsNumber("DISCOVERY_REFRESH_INTERVAL", 60000), + healthCheckTimeout: EnvLoader.getPropertyAsNumber("DISCOVERY_HEALTH_CHECK_TIMEOUT", 5000), + minArchiveNodes: EnvLoader.getPropertyAsNumber("DISCOVERY_MIN_ARCHIVE_NODES", 1), + maxRetries: EnvLoader.getPropertyAsNumber("DISCOVERY_MAX_CONNECTION_RETRIES", 3) + }; + } + + async initialize(vNodeId: string, archivalNodes: Map) { + this.vNodeId = vNodeId; + this.archivalNodes = archivalNodes; + + // Initial connection with retry mechanism + await this.ensureMinimumConnections(); + + // Start periodic refresh + this.refreshInterval = setInterval( + () => this.refreshNodeStatus(), + this.config.refreshInterval + ); + } + + private async ensureMinimumConnections(): Promise { + let retryCount = 0; + + while (this.archiveNodes.size < this.config.minArchiveNodes && retryCount < this.config.maxRetries) { + this.log.info(`Attempting to establish minimum ANode connections. Current: ${this.archiveNodes.size}, Target: ${this.config.minArchiveNodes}`); + + // Get available nodes that aren't already connected + const availableNodes = Array.from(this.archivalNodes.entries()) + .filter(([nodeId]) => !this.archiveNodes.has(nodeId)); + + if (availableNodes.length === 0) { + this.log.error('No more available nodes to connect to'); + break; + } + + // Shuffle available nodes for random selection + const shuffledNodes = this.shuffleArray(availableNodes); + + // Try to connect to nodes until we reach minimum or run out of nodes + for (const [nodeId, nodeInfo] of shuffledNodes) { + if (this.archiveNodes.size >= this.config.minArchiveNodes) break; + + const wsUrl = nodeInfo.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); + await this.addNode(wsUrl, nodeInfo); + } + + retryCount++; + + if (this.archiveNodes.size < this.config.minArchiveNodes) { + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait before retry + } + } + + if (this.archiveNodes.size < this.config.minArchiveNodes) { + this.log.error(`Failed to establish minimum required connections. Current: ${this.archiveNodes.size}, Required: ${this.config.minArchiveNodes}`); + } + } + + private shuffleArray(array: T[]): T[] { + const shuffled = [...array]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + return shuffled; + } + + async getActiveArchiveNodes(): Promise { + const nodes = Array.from(this.archiveNodes.values()); + return nodes.filter(node => node.nodeStatus === 0); + } + + private async addNode(wsUrl: string, nodeInfo: NodeInfo) { + try { + const isHealthy = await this.checkNodeHealth(wsUrl); + if (isHealthy) { + this.archiveNodes.set(nodeInfo.nodeId, nodeInfo); + this.log.info(`Successfully added healthy ANode ${nodeInfo.nodeId}`); + } else { + this.log.warn(`ANode ${nodeInfo.nodeId} not added - health check failed`); + } + } catch (error) { + this.log.error(`Failed to add node ${wsUrl}:`, error); + } + } + + private async checkNodeHealth(wsUrl: string): Promise { + return new Promise((resolve) => { + try { + const ws = new WebSocket(wsUrl, { + headers: { + 'validator-address': this.vNodeId + } + }); + + ws.on('open', () => { + this.log.info(`WebSocket connection opened to ANode at ${wsUrl}`); + }); + + const timeout = setTimeout(() => { + this.log.warn(`Health check timed out for ANode at ${wsUrl}`); + cleanup(); + ws.close(); + resolve(false); + }, this.config.healthCheckTimeout); + + ws.on('message', (data: Buffer) => { + try { + const message = JSON.parse(data.toString()); + this.log.debug(`Received message from ANode at ${wsUrl}:`, message); + + if (message.type === 'AUTH_CHALLENGE' && message.nonce) { + const healthCheck = { + type: 'HEALTH_CHECK', + timestamp: Date.now() + }; + this.log.info(`Sending health check to ANode at ${wsUrl}: ${healthCheck.timestamp}`); + ws.send(JSON.stringify(healthCheck)); + } else if (message.type === 'HEALTH_CHECK_RESPONSE') { + this.log.info(`Health check is successful from ANode at ${wsUrl}: ${message.timestamp}`); + cleanup(); + ws.close(1000, 'Health check complete'); + resolve(true); + } + } catch (error) { + this.log.error(`Failed to process message from ANode at ${wsUrl}:`, error); + cleanup(); + ws.close(); + resolve(false); + } + }); + + ws.on('error', (error) => { + this.log.error(`WebSocket error for ANode at ${wsUrl}:`, error); + cleanup(); + resolve(false); + }); + + const cleanup = () => { + clearTimeout(timeout); + ws.removeAllListeners(); + }; + } catch (error) { + this.log.error(`Failed to establish WebSocket connection to ANode at ${wsUrl}:`, error); + resolve(false); + } + }); + } + + private async refreshNodeStatus() { + const updatedNodes = new Map(); + + // First check existing nodes + for (const [nodeId, nodeInfo] of this.archiveNodes.entries()) { + const wsUrl = nodeInfo.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); + const isHealthy = await this.checkNodeHealth(wsUrl); + if (isHealthy) { + updatedNodes.set(nodeId, { + ...nodeInfo, + nodeStatus: 0 + }); + } else { + this.log.warn(`ANode at ${wsUrl} is no longer healthy, removing from active nodes`); + } + } + + // If we're below minimum, try to add new nodes + while (updatedNodes.size < this.config.minArchiveNodes) { + // Get available nodes that aren't already in use + const availableNodes = Array.from(this.archivalNodes.entries()) + .filter(([nodeId]) => !updatedNodes.has(nodeId)); + + if (availableNodes.length === 0) { + this.log.error('No more available ANodes to try'); + break; + } + + // Pick a random available node + const shuffledNodes = this.shuffleArray(availableNodes); + const [nodeId, nodeInfo] = shuffledNodes[0]; + + const wsUrl = nodeInfo.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); + const isHealthy = await this.checkNodeHealth(wsUrl); + + if (isHealthy) { + updatedNodes.set(nodeId, { + ...nodeInfo, + nodeStatus: 0 + }); + this.log.info(`Added new healthy ANode ${nodeId}`); + } else { + this.log.warn(`Attempted ANode ${nodeId} is not healthy, trying another`); + } + } + + // Update the archive nodes map + this.archiveNodes = updatedNodes; + + if (this.archiveNodes.size < this.config.minArchiveNodes) { + this.log.error(`Failed to maintain minimum required archive nodes. Current: ${this.archiveNodes.size}, Required: ${this.config.minArchiveNodes}`); + } else { + this.log.info(`Successfully maintaining ${this.archiveNodes.size} healthy archive nodes`); + } + } + + // Cleanup + async destroy() { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + } + } +} diff --git a/src/services/WebSockets/WebSocketClient.ts b/src/services/WebSockets/WebSocketClient.ts new file mode 100644 index 0000000..ff12fa5 --- /dev/null +++ b/src/services/WebSockets/WebSocketClient.ts @@ -0,0 +1,412 @@ +import { Service } from 'typedi'; +import WebSocket from 'ws'; +import { WinstonUtil } from "../../utilz/winstonUtil"; +import { ArchiveNodeInfo, WSMessage } from './types'; +import { DiscoveryService } from './DiscoverService'; +import { BlockStatusManager } from './BlockStatusManager'; +import crypto from 'crypto'; +import { NodeInfo } from '../messaging-common/validatorContractState'; +import { BitUtil } from '../../utilz/bitUtil'; +import { EthUtil } from '../../utilz/ethUtil'; +import { Wallet } from 'ethers'; +interface AuthChallenge { + type: 'AUTH_CHALLENGE'; + nonce: string; +} + +interface AuthResponse { + type: 'AUTH_RESPONSE'; + nonce: string; + signature: string; + validatorAddress: string; +} + +interface AuthSuccess { + type: 'AUTH_SUCCESS'; +} + +@Service() +export class WebSocketClient { + private readonly MIN_ARCHIVE_CONNECTIONS = 1; + private archiveConnections = new Map(); + private readonly log = WinstonUtil.newLog("WebSocketClient"); + private reconnectTimer: NodeJS.Timer; + private readonly maxReconnectAttempts = 5; + private readonly baseReconnectDelay = 1000; // Start with 1 second + private readonly nodeId: string; + private vNodeId: string; + private wallet: Wallet; + private reconnectAttempts = new Map(); + private readonly CONNECTION_TIMEOUT = 65000; // 65 seconds (matches server) + private readonly PING_INTERVAL = 60000; // 60 seconds (matches server's HEARTBEAT_INTERVAL) + private readonly MONITOR_INTERVAL = 30000; // 30 seconds (check more frequently than ping interval) + private readonly MAX_RECONNECT_DELAY = 32000; // 32 seconds (this is fine) + private connectionStates = new Map(); + + constructor( + private readonly discoveryService: DiscoveryService, + private readonly blockManager: BlockStatusManager, + ) {} + + async postConstruct(vNodeId: string, wallet: Wallet) { + try { + this.log.info('WebSocket client postConstruct started'); + this.vNodeId = vNodeId; + this.wallet = wallet; + await this.initializeConnections(); + this.startConnectionMonitoring(); + this.log.info('WebSocket client initialized'); + } catch (error) { + this.log.error('Failed to initialize WebSocket client:', error); + throw error; + } + } + + private async initializeConnections() { + this.log.info('Initializing archive node connections'); + const activeNodes = await this.discoveryService.getActiveArchiveNodes(); + + if (activeNodes.length < this.MIN_ARCHIVE_CONNECTIONS) { + throw new Error(`Insufficient Active Archive Nodes. Need ${this.MIN_ARCHIVE_CONNECTIONS}, got ${activeNodes.length}`); + } + + for (const node of activeNodes) { + if (this.archiveConnections.size < this.MIN_ARCHIVE_CONNECTIONS) { + await this.connectToArchiveNode(node); // Pass the entire node info + } + } + } + + private async connectToArchiveNode(node: NodeInfo) { + if (this.archiveConnections.has(node.nodeId)) { + this.log.warn(`Already connected to node ${node.nodeId}`); + return; + } + + const wsUrl = node.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); + + return new Promise((resolve, reject) => { + const ws = new WebSocket(wsUrl, { + headers: { + 'validator-address': this.vNodeId + } + }); + + const timeout = setTimeout(() => { + cleanup(); + ws.terminate(); + reject(new Error(`[${this.vNodeId}] Connection timeout: ${node.nodeId}`)); + }, this.CONNECTION_TIMEOUT); + + // Handle authentication flow + const handleAuth = async (data: Buffer) => { + try { + const message = JSON.parse(data.toString()); + + if (message.type === 'AUTH_CHALLENGE') { + // Sign the nonce using EthUtil + this.log.info(`Received auth challenge from ${node.nodeId}: ${message.nonce}`); + + const signatureBytes = await EthUtil.signBytes( + this.wallet, + BitUtil.base16ToBytes(message.nonce) + ); + const signature = BitUtil.bytesToBase16(signatureBytes); + + // Send auth response + const response: AuthResponse = { + type: 'AUTH_RESPONSE', + nonce: message.nonce, + signature, + validatorAddress: this.vNodeId + }; + this.log.info(`Sending auth response to ${node.nodeId}:`, response); + ws.send(JSON.stringify(response)); + } else if (message.type === 'AUTH_SUCCESS') { + this.log.info(`Authentication successful for ${node.nodeId}`); + ws.removeListener('message', handleAuth); + cleanup(); + this.archiveConnections.set(node.nodeId, ws); + this.setupWebSocketHandlers(node.nodeId, ws); + this.subscribeToEvents(ws, node.nodeId); + this.reconnectAttempts.delete(node.nodeId); + resolve(); + } + } catch (error) { + cleanup(); + ws.terminate(); + reject(new Error(`Authentication failed: ${error.message}`)); + } + }; + + const onOpen = () => { + // Wait for auth challenge + ws.on('message', handleAuth); + }; + + const onError = (error: Error) => { + cleanup(); + ws.terminate(); + this.log.error(`[${this.vNodeId}] Connection error to ${node.nodeId}:`, error); + reject(error); + }; + + const cleanup = () => { + clearTimeout(timeout); + ws.removeListener('open', onOpen); + ws.removeListener('error', onError); + ws.removeListener('message', handleAuth); + }; + + ws.on('open', onOpen); + ws.on('error', onError); + }); + } + + private subscribeToEvents(ws: WebSocket, nodeId: string) { + const subscribeMessage: WSMessage = { + type: 'SUBSCRIBE', + nodeId: this.vNodeId, // Use validator's address + sourceNodeId: nodeId, + nodeType: 'VALIDATOR', + events: ['BLOCK_STORED'] + }; + + try { + ws.send(JSON.stringify(subscribeMessage)); + this.log.debug(`vNode ${this.vNodeId} subscribed to events on anode ${nodeId}:`, subscribeMessage); + } catch (error) { + this.log.error(`vNode ${this.vNodeId} failed to subscribe to events on anode ${nodeId}:`, error); + ws.close(); + } + } + + private handleArchiveMessage(nodeId: string, message: WSMessage) { + console.log(`Received message from ${nodeId}:`, message); + this.log.debug(`Received message from ${nodeId}:`, message); + + if (message.type === 'BLOCK_STORED' && message.data) { + this.blockManager.handleBlockConfirmation( + message.data.hash, + nodeId, + message.data + ); + } + } + + private setupWebSocketHandlers(nodeId: string, ws: WebSocket) { + const messageHandler = (data: string) => { + try { + const message = JSON.parse(data) as WSMessage; + this.handleArchiveMessage(nodeId, message); + } catch (error) { + this.log.error(`Message parsing error from ${nodeId}:`, error); + } + }; + + const closeHandler = async (event: { code: number; reason: string }) => { + this.log.warn(`Disconnected from Archive Node: ${nodeId}. Code: ${event.code}, Reason: ${event.reason}`); + cleanup(); + this.archiveConnections.delete(nodeId); + + // Force terminate if close takes too long + const terminateTimeout = setTimeout(() => { + ws.terminate(); + }, 5000); + + // Attempt immediate reconnection for unexpected closures + if (event.code !== 1000) { // 1000 is normal closure + await this.handleDisconnect(nodeId); + } + + clearTimeout(terminateTimeout); + }; + + const errorHandler = (error: Error) => { + this.log.error(`WebSocket error for ${nodeId}:`, error); + ws.close(); + }; + + // Add ping/pong tracking + ws.on('ping', () => { + this.updateConnectionState(nodeId, { + lastPing: Date.now() + }); + }); + + ws.on('pong', () => { + this.updateConnectionState(nodeId, { + lastPong: Date.now() + }); + }); + + // Setup heartbeat with state tracking + const pingInterval = setInterval(() => { + if (ws.readyState === WebSocket.OPEN) { + this.updateConnectionState(nodeId, { lastPing: Date.now() }); + ws.ping((err) => { + if (err) { + this.log.warn(`Ping failed for ${nodeId}:`, err); + ws.close(); + } + }); + } + }, this.PING_INTERVAL); + + const cleanup = () => { + clearInterval(pingInterval); + ws.removeListener('message', messageHandler); + ws.removeListener('close', closeHandler); + ws.removeListener('error', errorHandler); + }; + + ws.on('message', messageHandler); + ws.on('close', closeHandler); + ws.on('error', errorHandler); + } + + private async handleDisconnect(nodeId: string) { + const attempts = this.reconnectAttempts.get(nodeId) || 0; + + this.updateConnectionState(nodeId, { isReconnecting: true }); + + if (attempts >= this.maxReconnectAttempts) { + this.log.error(`Max reconnection attempts reached for node ${nodeId}`); + this.reconnectAttempts.delete(nodeId); + this.updateConnectionState(nodeId, { isReconnecting: false }); + await this.findAlternativeNode(); + return; + } + + const delay = this.getReconnectDelay(attempts); // Use the helper method + this.log.info(`Attempting to reconnect to ${nodeId} in ${delay}ms (attempt ${attempts + 1}/${this.maxReconnectAttempts})`); + + await new Promise(resolve => setTimeout(resolve, delay)); + + try { + const nodes = await this.discoveryService.getActiveArchiveNodes(); + const nodeInfo = nodes.find(n => n.nodeId === nodeId); + + if (nodeInfo) { + await this.connectToArchiveNode(nodeInfo); + this.log.info(`Successfully reconnected to ${nodeId}`); + } else { + throw new Error(`Node ${nodeId} no longer available`); + } + } catch (error) { + this.log.error(`Failed to reconnect to ${nodeId}:`, error); + this.reconnectAttempts.set(nodeId, attempts + 1); + + if ((attempts + 1) >= this.maxReconnectAttempts) { + await this.findAlternativeNode(); + } + } + } + + private async findAlternativeNode() { + try { + const activeNodes = await this.discoveryService.getActiveArchiveNodes(); + const unusedNodes = activeNodes.filter(node => + !this.archiveConnections.has(node.nodeId) + ); + + if (unusedNodes.length > 0) { + this.log.info('Attempting to connect to alternative node'); + await this.connectToArchiveNode(unusedNodes[0]); + } + } catch (error) { + this.log.error('Failed to find alternative node:', error); + } + } + + private startConnectionMonitoring() { + this.reconnectTimer = setInterval(() => { + this.checkAndRestoreConnections(); + }, this.MONITOR_INTERVAL); // Check connections every minute + } + + async shutdown() { + try { + if (this.reconnectTimer) { + clearInterval(this.reconnectTimer); + } + + const closePromises = Array.from(this.archiveConnections.entries()).map(([nodeId, ws]) => { + return new Promise((resolve) => { + this.log.info(`Closing connection to ${nodeId}`); + ws.once('close', () => resolve()); + ws.close(1000, 'Shutdown requested'); + }); + }); + + await Promise.all(closePromises); + + this.archiveConnections.clear(); + this.reconnectAttempts.clear(); + } catch (error) { + this.log.error('Error during shutdown:', error); + } + } + + private async checkAndRestoreConnections() { + try { + // First validate existing connections + await this.validateConnections(); + + const activeConnections = Array.from(this.archiveConnections.entries()) + .filter(([_, ws]) => ws.readyState === WebSocket.OPEN); + + if (activeConnections.length < this.MIN_ARCHIVE_CONNECTIONS) { + this.log.warn(`Active connections (${activeConnections.length}) below minimum (${this.MIN_ARCHIVE_CONNECTIONS})`); + await this.initializeConnections(); + } + } catch (error) { + this.log.error('Failed to restore connections:', error); + } + } + + private isConnectionValid(ws: WebSocket): boolean { + return ws.readyState === WebSocket.OPEN; + } + + private async validateConnections() { + const now = Date.now(); + for (const [nodeId, ws] of this.archiveConnections) { + const state = this.connectionStates.get(nodeId); + + // Check if connection is stale (no pong received within timeout) + if (state?.lastPong && (now - state.lastPong > this.CONNECTION_TIMEOUT)) { + this.log.warn(`Connection stale for ${nodeId} - no pong received`); + ws.close(); + this.archiveConnections.delete(nodeId); + continue; + } + + if (!this.isConnectionValid(ws)) { + this.log.warn(`Invalid connection detected for ${nodeId}`); + ws.close(); + this.archiveConnections.delete(nodeId); + } + } + } + + private getReconnectDelay(attempts: number): number { + const delay = this.baseReconnectDelay * Math.pow(2, attempts); + return Math.min(delay, this.MAX_RECONNECT_DELAY); + } + + private updateConnectionState(nodeId: string, update: Partial<{ + lastPing: number; + lastPong: number; + isReconnecting: boolean; + }>) { + const current = this.connectionStates.get(nodeId) || { + isReconnecting: false + }; + this.connectionStates.set(nodeId, { ...current, ...update }); + } +} \ No newline at end of file diff --git a/src/services/WebSockets/WebSocketManager.ts b/src/services/WebSockets/WebSocketManager.ts new file mode 100644 index 0000000..37b62ec --- /dev/null +++ b/src/services/WebSockets/WebSocketManager.ts @@ -0,0 +1,75 @@ +// src/services/WebSockets/WebSocketManager.ts +import { Service } from 'typedi'; +import { WinstonUtil } from "../../utilz/winstonUtil"; +import { WebSocketClient } from './WebSocketClient'; +import { WebSocketServer } from './WebSocketServer'; +import { DiscoveryService } from './DiscoverService'; +import { BlockStatusManager } from './BlockStatusManager'; +import { NodeInfo } from '../messaging-common/validatorContractState'; +import { Wallet } from 'ethers' + +@Service() +export class WebSocketManager { + private readonly log = WinstonUtil.newLog(WebSocketManager); + private cleanupInterval?: NodeJS.Timeout; + + constructor( + private readonly wsClient: WebSocketClient, + private readonly wsServer: WebSocketServer, + private readonly blockManager: BlockStatusManager, + private readonly discoveryService: DiscoveryService + ) {} + + async postConstruct(vNodeId: string, wallet: Wallet, archivalNodes: Map) { + try { + if (!archivalNodes) { + throw new Error('archivalNodes is undefined'); + } + + await this.discoveryService.initialize(vNodeId, archivalNodes); + await this.wsServer.postConstruct(); + await this.wsClient.postConstruct(vNodeId, wallet); + + // Store interval reference + this.cleanupInterval = setInterval(() => { + this.blockManager.performCleanup(); + }, 60 * 60 * 1000); // Every hour + + let artwork = + ` + ____ _ __ __ _ _ _ _ +| _ \\ _ _ ___| |__ \\ \\ / /_ _| (_) __| | __ _| |_ ___ _ __ +| |_) | | | / __| '_ \\ \\ V / _\` | | |/ _\` |/ _\` | __/ _ \\| '__| +| __/| |_| \\__ \\ | | | | | (_| | | | (_| | (_| | || (_) | | +|_| \\__,_|___/_| |_| |_|\\__,_|_|_|\\__,_|\\__,_|\\__\\___/|_| +__ __ _ ____ _ _ +\\ \\ / /__| |__/ ___| ___ ___| | _____| |_ + \\ \\ /\\ / / _ \\ '_ \\___ \\ / _ \\/ __| |/ / _ \\ __| + \\ V V / __/ |_) |__) | __/ (__| < __/ |_ + \\_/\\_/ \\___|_.__/____/ \\___|\\___|_|\\_\\___|\\__| +`; + + console.log(` + ################################################ + ${artwork} + + 🛡️ WebSocket Server and Client Initialized Successfully 🛡️ + ################################################ + `); + } catch (error) { + this.log.error('Failed to initialize WebSocket manager:', error); + throw error; + } + } + + async shutdown() { + // Clear the interval + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + } + + // Cleanup components + await this.wsClient.shutdown(); + await this.wsServer.shutdown(); + } +} \ No newline at end of file diff --git a/src/services/WebSockets/WebSocketServer.ts b/src/services/WebSockets/WebSocketServer.ts new file mode 100644 index 0000000..79dc242 --- /dev/null +++ b/src/services/WebSockets/WebSocketServer.ts @@ -0,0 +1,132 @@ +// src/services/WebSockets/WebSocketServer.ts +import { Service } from 'typedi'; +import WebSocket from 'ws'; +import { WinstonUtil } from "../../utilz/winstonUtil"; +import { EnvLoader } from "../../utilz/envLoader"; +import { ClientInfo, BlockData, WSMessage } from './types'; + +interface WSClientConnection { + ws: WebSocket; + subscriptions: Set; + connectedAt: number; +} + +@Service() +export class WebSocketServer { + private wss: WebSocket.Server; + private clients = new Map(); + private readonly log = WinstonUtil.newLog("WebSocketServer"); + + async postConstruct() { + try { + await this.initialize(); + this.log.info('WebSocket server initialized'); + } catch (error) { + this.log.error('Failed to initialize WebSocket server:', error); + throw error; + } + } + + private async initialize() { + const port = EnvLoader.getPropertyAsNumber('WS_PORT', 8080); + + this.wss = new WebSocket.Server({ + port, + maxPayload: EnvLoader.getPropertyAsNumber('WS_MAX_PAYLOAD', 5 * 1024 * 1024) + }); + + this.wss.on('connection', (ws: WebSocket) => { + const clientId = this.generateClientId(); + this.handleClientConnection(clientId, ws); + }); + + this.log.info(`WebSocket server listening on port ${port}`); + } + + private handleClientConnection(clientId: string, ws: WebSocket) { + this.log.info(`New client connected: ${clientId}`); + this.clients.set(clientId, { + ws, + subscriptions: new Set(), + connectedAt: Date.now() + }); + + ws.on('message', (data: string) => { + try { + const message = JSON.parse(data) as WSMessage; + this.handleClientMessage(clientId, message); + } catch (error) { + this.log.error(`Failed to parse message from client ${clientId}:`, error); + } + }); + + ws.on('close', () => { + this.log.info(`Client disconnected: ${clientId}`); + this.clients.delete(clientId); + }); + + ws.on('error', (error) => { + this.log.error(`Client ${clientId} error:`, error); + this.clients.delete(clientId); + }); + + // Setup heartbeat + const pingInterval = setInterval(() => { + if (ws.readyState === WebSocket.OPEN) { + ws.ping(); + } + }, 30000); + + ws.on('close', () => clearInterval(pingInterval)); + } + + private handleClientMessage(clientId: string, message: WSMessage) { + switch (message.type) { + case 'SUBSCRIBE': + const client = this.clients.get(clientId); + if (client && typeof message.data === 'string') { + client.subscriptions.add(message.data); + this.log.debug(`Client ${clientId} subscribed to ${message.data}`); + } + break; + } + } + + public broadcastBlockUpdate(blockData: BlockData) { + const message: WSMessage = { + type: 'BLOCK_STORED', + data: blockData, + timestamp: Date.now(), + nodeId: blockData.nodeId, + nodeType: blockData.nodeType, + events: [] + }; + + this.clients.forEach((client, clientId) => { + if (client.ws.readyState === WebSocket.OPEN && + client.subscriptions.has('BLOCK_STORED')) { + try { + client.ws.send(JSON.stringify(message)); + this.log.debug(`Sent block update to client ${clientId}`); + } catch (error) { + this.log.error(`Failed to send to client ${clientId}:`, error); + } + } + }); + } + + private generateClientId(): string { + return `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; + } + + async shutdown() { + for (const [clientId, client] of this.clients) { + this.log.info(`Closing connection to client ${clientId}`); + client.ws.close(); + } + + if (this.wss) { + await new Promise((resolve) => this.wss.close(() => resolve())); + } + } +} \ No newline at end of file diff --git a/src/services/WebSockets/types.ts b/src/services/WebSockets/types.ts new file mode 100644 index 0000000..5e51063 --- /dev/null +++ b/src/services/WebSockets/types.ts @@ -0,0 +1,48 @@ +import WebSocket from 'ws'; + +// src/services/WebSockets/types.ts +export interface BlockData { + hash: string; + number: number; + timestamp: number; + nodeId: string; + nodeType: string; + // ... other block data +} + +export interface WSMessage { + type: string; + nodeId: string; // ID of the subscriber (vNode) + sourceNodeId?: string; // ID of the anode being subscribed to + nodeType: string; + events: string[]; + data?: any; + timestamp?: number; +} + +export interface ArchiveNodeInfo { + id: string; + wsUrl: string; + status: 'ACTIVE' | 'INACTIVE'; + lastSeen?: number; +} + +export interface ClientInfo { + ws: WebSocket; + subscriptions: Set; + connectedAt: number; +} + +export interface ArchiveNodeInfo { + id: string; + wsUrl: string; + status: 'ACTIVE' | 'INACTIVE'; + lastSeen?: number; +} + +export interface DiscoveryConfig { + refreshInterval: number; + healthCheckTimeout: number; + minArchiveNodes: number; + maxRetries: number; +} \ No newline at end of file diff --git a/src/utilz/envLoader.ts b/src/utilz/envLoader.ts index 61bdebb..d4ad1ed 100644 --- a/src/utilz/envLoader.ts +++ b/src/utilz/envLoader.ts @@ -51,4 +51,9 @@ export class EnvLoader { const val = process.env[propName] return NumUtil.parseInt(val, defaultValue); } + + static getPropertyAsArray(key: string, defaultValue: string[] = []): string[] { + const value = process.env[key]; + return value ? value.split(',').map(item => item.trim()) : defaultValue; + } } diff --git a/tsconfig.json b/tsconfig.json index 34cfea0..1c5f775 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "target": "es2017", "lib": ["es2017", "esnext.asynciterable"], - "typeRoots": ["./node_modules/@types", "./src/types", "./src/generated"], + "typeRoots": ["./node_modules/@types", "src/services/WebSockets/types", "./src/generated"], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, diff --git a/yarn.lock b/yarn.lock index 7bc50ca..94e921c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,7 +28,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz" integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== -"@babel/core@^7.7.5": +"@babel/core@^7.0.0", "@babel/core@^7.7.5": version "7.25.2" resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz" integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== @@ -138,7 +138,7 @@ "@babel/runtime@^7.25.0": version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz" integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== dependencies: regenerator-runtime "^0.14.0" @@ -176,10 +176,10 @@ "@bufbuild/protobuf@^2.0.0": version "2.2.0" - resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-2.2.0.tgz#3c63dcbb6ef4f6a9f587e3d412c08f9a8f5bcd00" + resolved "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz" integrity sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ== -"@colors/colors@1.6.0", "@colors/colors@^1.6.0": +"@colors/colors@^1.6.0", "@colors/colors@1.6.0": version "1.6.0" resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== @@ -239,14 +239,14 @@ "@ethereumjs/util@^8.0.6": version "8.1.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + resolved "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz" integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== dependencies: "@ethereumjs/rlp" "^4.0.1" ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@^5.7.0", "@ethersproject/abi@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -261,7 +261,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": +"@ethersproject/abstract-provider@^5.7.0", "@ethersproject/abstract-provider@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz" integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== @@ -274,7 +274,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": +"@ethersproject/abstract-signer@^5.7.0", "@ethersproject/abstract-signer@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz" integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== @@ -285,7 +285,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": +"@ethersproject/address@^5.7.0", "@ethersproject/address@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz" integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== @@ -296,14 +296,14 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp" "^5.7.0" -"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": +"@ethersproject/base64@^5.7.0", "@ethersproject/base64@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz" integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== dependencies: "@ethersproject/bytes" "^5.7.0" -"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": +"@ethersproject/basex@^5.7.0", "@ethersproject/basex@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz" integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== @@ -311,7 +311,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": +"@ethersproject/bignumber@^5.7.0", "@ethersproject/bignumber@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz" integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== @@ -320,14 +320,14 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": +"@ethersproject/bytes@^5.7.0", "@ethersproject/bytes@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz" integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": +"@ethersproject/constants@^5.7.0", "@ethersproject/constants@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz" integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== @@ -350,7 +350,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/transactions" "^5.7.0" -"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": +"@ethersproject/hash@^5.7.0", "@ethersproject/hash@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz" integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== @@ -365,7 +365,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": +"@ethersproject/hdnode@^5.7.0", "@ethersproject/hdnode@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz" integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== @@ -383,7 +383,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": +"@ethersproject/json-wallets@^5.7.0", "@ethersproject/json-wallets@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz" integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== @@ -402,7 +402,7 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": +"@ethersproject/keccak256@^5.7.0", "@ethersproject/keccak256@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz" integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== @@ -410,19 +410,19 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": +"@ethersproject/logger@^5.7.0", "@ethersproject/logger@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": +"@ethersproject/networks@^5.7.0", "@ethersproject/networks@5.7.1": version "5.7.1" resolved "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz" integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": +"@ethersproject/pbkdf2@^5.7.0", "@ethersproject/pbkdf2@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz" integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== @@ -430,7 +430,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" -"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": +"@ethersproject/properties@^5.7.0", "@ethersproject/properties@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz" integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== @@ -463,7 +463,7 @@ bech32 "1.1.4" ws "7.4.6" -"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": +"@ethersproject/random@^5.7.0", "@ethersproject/random@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz" integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== @@ -471,7 +471,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": +"@ethersproject/rlp@^5.7.0", "@ethersproject/rlp@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz" integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== @@ -479,7 +479,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": +"@ethersproject/sha2@^5.7.0", "@ethersproject/sha2@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz" integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== @@ -488,7 +488,7 @@ "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" -"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": +"@ethersproject/signing-key@^5.7.0", "@ethersproject/signing-key@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz" integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== @@ -512,7 +512,7 @@ "@ethersproject/sha2" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": +"@ethersproject/strings@^5.7.0", "@ethersproject/strings@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz" integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== @@ -521,7 +521,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": +"@ethersproject/transactions@^5.7.0", "@ethersproject/transactions@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== @@ -566,7 +566,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": +"@ethersproject/web@^5.7.0", "@ethersproject/web@5.7.1": version "5.7.1" resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz" integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== @@ -577,7 +577,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": +"@ethersproject/wordlists@^5.7.0", "@ethersproject/wordlists@5.7.0": version "5.7.0" resolved "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz" integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== @@ -666,14 +666,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" @@ -682,6 +674,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@livepeer/core@^1.9.2": version "1.9.2" resolved "https://registry.npmjs.org/@livepeer/core/-/core-1.9.2.tgz" @@ -712,58 +712,65 @@ dependencies: sparse-bitfield "^3.0.3" -"@noble/curves@1.2.0", "@noble/curves@~1.2.0": +"@noble/curves@^1.4.2": + version "1.6.0" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + +"@noble/curves@~1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz" integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== dependencies: "@noble/hashes" "1.3.2" -"@noble/curves@1.4.2", "@noble/curves@~1.4.0": +"@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + +"@noble/curves@~1.4.0", "@noble/curves@1.4.2": version "1.4.2" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz" integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@^1.4.2": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" - integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== - dependencies: - "@noble/hashes" "1.5.0" - -"@noble/curves@~1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" - integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== dependencies: - "@noble/hashes" "1.3.3" + "@noble/hashes" "1.3.2" "@noble/ed25519@^2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-2.1.0.tgz#4bf661de9ee0ad775d41fcacbfc9aeec491f459c" + resolved "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.1.0.tgz" integrity sha512-KM4qTyXPinyCgMzeYJH/UudpdL+paJXtY3CHtHYZQtBkS8MZoPr4rOikZllIutJe0d06QDQKisyn02gxZ8TcQA== -"@noble/hashes@1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" - integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@^1.4.0", "@noble/hashes@1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== -"@noble/hashes@1.3.3", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2", "@noble/hashes@~1.3.3": +"@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2", "@noble/hashes@~1.3.3", "@noble/hashes@1.3.3": version "1.3.3" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== -"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": +"@noble/hashes@~1.4.0", "@noble/hashes@1.4.0": version "1.4.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" - integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -773,7 +780,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -813,7 +820,7 @@ resolved "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz" integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg== -"@redis/client@1.6.0": +"@redis/client@^1.0.0", "@redis/client@1.6.0": version "1.6.0" resolved "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz" integrity sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg== @@ -844,7 +851,7 @@ "@rodrigogs/mysql-events@0.6.0": version "0.6.0" - resolved "https://registry.yarnpkg.com/@rodrigogs/mysql-events/-/mysql-events-0.6.0.tgz#5026ca824c7f6e6843c4f6456b032edb526fe7f5" + resolved "https://registry.npmjs.org/@rodrigogs/mysql-events/-/mysql-events-0.6.0.tgz" integrity sha512-bI4zECD76lprG050XIl9+ZY2COZ7rxwtT982uF+aV872j/zbQC0imYDZuYcQuktNC3GyLuo+wH/BoxrrfDQEag== dependencies: "@rodrigogs/zongji" "^0.4.14" @@ -854,7 +861,7 @@ "@rodrigogs/zongji@^0.4.14": version "0.4.14" - resolved "https://registry.yarnpkg.com/@rodrigogs/zongji/-/zongji-0.4.14.tgz#0dde5fbdf0f7573e2f8c157d47eb9c0e4522a59b" + resolved "https://registry.npmjs.org/@rodrigogs/zongji/-/zongji-0.4.14.tgz" integrity sha512-U2UUFzL/Lsc7AfRXAyo2OW4caGzf+teKOVA+D1Suq4f87Scn4frIGaiKOgZ/j50C1bXRT46wcy2AgWgZfr5fWQ== dependencies: iconv-lite "^0.4.24" @@ -867,7 +874,7 @@ "@scure/base@~1.1.3": version "1.1.9" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz" integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== "@scure/bip32@1.3.2": @@ -906,7 +913,7 @@ "@scure/starknet@~1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-1.0.0.tgz#4419bc2fdf70f3dd6cb461d36c878c9ef4419f8c" + resolved "https://registry.npmjs.org/@scure/starknet/-/starknet-1.0.0.tgz" integrity sha512-o5J57zY0f+2IL/mq8+AYJJ4Xpc1fOtDhr+mFQKbHnYFmm3WQrC+8zj2HEgxak1a+x86mhmBC1Kq305KUpVf0wg== dependencies: "@noble/curves" "~1.3.0" @@ -971,14 +978,14 @@ "@solana/buffer-layout@^4.0.1": version "4.0.1" - resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" + resolved "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz" integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== dependencies: buffer "~6.0.3" "@solana/web3.js@^1.95.3": version "1.95.3" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.3.tgz#70b5f4d76823f56b5af6403da51125fffeb65ff3" + resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.95.3.tgz" integrity sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og== dependencies: "@babel/runtime" "^7.25.0" @@ -997,11 +1004,10 @@ rpc-websockets "^9.0.2" superstruct "^2.0.2" -"@starknet-io/types-js@^0.7.7", "starknet-types-07@npm:@starknet-io/types-js@^0.7.7": - name starknet-types-07 - version "0.7.7" - resolved "https://registry.yarnpkg.com/@starknet-io/types-js/-/types-js-0.7.7.tgz#444be5e4e585ec6f599d42d3407280d98b2dfdf8" - integrity sha512-WLrpK7LIaIb8Ymxu6KF/6JkGW1sso988DweWu7p5QY/3y7waBIiPvzh27D9bX5KIJNRDyOoOVoHVEKYUYWZ/RQ== +"@starknet-io/types-js@^0.7.7": + version "0.7.10" + resolved "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz" + integrity sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w== "@stitches/core@^1.2.8": version "1.2.8" @@ -1010,7 +1016,7 @@ "@swc/helpers@^0.5.11": version "0.5.13" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz" integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w== dependencies: tslib "^2.4.0" @@ -1095,7 +1101,7 @@ "@types/json-schema@^7.0.9": version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/mime@^1": @@ -1148,7 +1154,7 @@ "@types/node@^12.12.54": version "12.20.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== "@types/qs@*": @@ -1185,12 +1191,12 @@ "@types/strip-bom@^3.0.0": version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + resolved "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== "@types/strip-json-comments@0.0.30": version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + resolved "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== "@types/triple-beam@^1.3.2": @@ -1200,7 +1206,7 @@ "@types/uuid@^8.3.4": version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/uuid@^9.0.0": @@ -1222,19 +1228,19 @@ "@types/ws@^7.4.4": version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + resolved "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz" integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== dependencies: "@types/node" "*" "@types/ws@^8.2.2": version "8.5.12" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz" integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^5.54.1": +"@typescript-eslint/eslint-plugin@^5.0.0", "@typescript-eslint/eslint-plugin@^5.54.1": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz" integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== @@ -1250,7 +1256,7 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.54.1": +"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.54.1": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz" integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== @@ -1328,17 +1334,9 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abi-wan-kanabi@^2.2.2: version "2.2.3" - resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-2.2.3.tgz#d1c410325aac866f31f3d589279a87b341e5641f" + resolved "https://registry.npmjs.org/abi-wan-kanabi/-/abi-wan-kanabi-2.2.3.tgz" integrity sha512-JlqiAl9CPvTm5kKG0QXmVCWNWoC/XyRMOeT77cQlbxXWllgjf6SqUmaNqFon72C2o5OSZids+5FvLdsw6dvWaw== dependencies: ansicolors "^0.3.2" @@ -1371,7 +1369,7 @@ acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: version "8.12.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -1383,7 +1381,7 @@ aes-js@3.0.0: agentkeepalive@^4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz" integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== dependencies: humanize-ms "^1.2.1" @@ -1398,7 +1396,7 @@ aggregate-error@^3.0.0: ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -1442,14 +1440,19 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.0.0, ansi-styles@^6.1.0: +ansi-styles@^6.0.0: + version "6.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== ansicolors@^0.3.2, ansicolors@~0.3.2: version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + resolved "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== anymatch@~3.1.2: @@ -1491,7 +1494,7 @@ argparse@^2.0.1: array-find-index@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + resolved "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz" integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== array-flatten@1.1.1: @@ -1516,7 +1519,7 @@ asn1.js@^5.0.0: assert-options@0.8.0: version "0.8.0" - resolved "https://registry.yarnpkg.com/assert-options/-/assert-options-0.8.0.tgz#cf71882534d23d3027945bc7462e20d3d3682380" + resolved "https://registry.npmjs.org/assert-options/-/assert-options-0.8.0.tgz" integrity sha512-qSELrEaEz4sGwTs4Qh+swQkjiHAysC4rot21+jzXU86dJzNG+FDqBzyS3ohSoTRf4ZLA3FSwxQdiuNl5NXUtvA== assertion-error@^1.1.0: @@ -1564,14 +1567,14 @@ balanced-match@^1.0.0: base-x@^3.0.2: version "3.0.10" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.10.tgz#62de58653f8762b5d6f8d9fe30fa75f7b2585a75" + resolved "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz" integrity sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ== dependencies: safe-buffer "^5.0.1" base-x@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b" + resolved "https://registry.npmjs.org/base-x/-/base-x-5.0.0.tgz" integrity sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ== base64-js@^1.0.2, base64-js@^1.3.1: @@ -1579,19 +1582,19 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + bech32@1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bech32@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" - integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== - bigint-buffer@^1.1.5: version "1.1.5" - resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" + resolved "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz" integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== dependencies: bindings "^1.3.0" @@ -1608,12 +1611,22 @@ binary-extensions@^2.0.0: bindings@^1.3.0: version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" -bn.js@^4.0.0, bn.js@^4.11.9, bn.js@^4.12.0: +bn.js@^4.0.0: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^4.12.0: version "4.12.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -1643,7 +1656,7 @@ body-parser@1.20.2: borsh@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + resolved "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz" integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== dependencies: bn.js "^5.2.0" @@ -1675,7 +1688,7 @@ browser-stdout@1.3.1: resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.23.1: +browserslist@^4.23.1, "browserslist@>= 4.21.0": version "4.23.3" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz" integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== @@ -1685,35 +1698,50 @@ browserslist@^4.23.1: node-releases "^2.0.18" update-browserslist-db "^1.1.0" -bs58@^4.0.0, bs58@^4.0.1: +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + resolved "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz" integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== dependencies: base-x "^3.0.2" bs58@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8" + resolved "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz" integrity sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw== dependencies: base-x "^5.0.0" bson@^6.7.0: version "6.8.0" - resolved "https://registry.yarnpkg.com/bson/-/bson-6.8.0.tgz#5063c41ba2437c2b8ff851b50d9e36cb7aaa7525" + resolved "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz" integrity sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ== buffer-from@^1.0.0, buffer-from@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-writer@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz" integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== +buffer@^6.0.3, buffer@~6.0.3, buffer@6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@4.9.2: version "4.9.2" resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz" @@ -1723,17 +1751,9 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - bufferutil@^4.0.1: version "4.0.8" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz" integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== dependencies: node-gyp-build "^4.3.0" @@ -1766,21 +1786,21 @@ call-bind@^1.0.0, call-bind@^1.0.7: caller-callsite@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + resolved "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz" integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== dependencies: callsites "^2.0.0" caller-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + resolved "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz" integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== dependencies: caller-callsite "^2.0.0" callsites@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + resolved "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz" integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== callsites@^3.0.0: @@ -1790,7 +1810,7 @@ callsites@^3.0.0: camelcase-keys@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz" integrity sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ== dependencies: camelcase "^2.0.0" @@ -1798,7 +1818,7 @@ camelcase-keys@^2.0.0: camelcase@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz" integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== camelcase@^5.0.0, camelcase@^5.3.1: @@ -1818,7 +1838,7 @@ caniuse-lite@^1.0.30001646: cardinal@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + resolved "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz" integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== dependencies: ansicolors "~0.3.2" @@ -1826,7 +1846,7 @@ cardinal@^2.1.1: case-anything@^2.1.13: version "2.1.13" - resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.13.tgz#0cdc16278cb29a7fcdeb072400da3f342ba329e9" + resolved "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz" integrity sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng== celebrate@^15.0.1: @@ -1851,14 +1871,9 @@ chai@^4.3.6: pathval "^1.1.1" type-detect "^4.1.0" -chalk@5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - chalk@^2.4.2: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -1873,6 +1888,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + check-error@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz" @@ -1880,10 +1900,10 @@ check-error@^1.0.3: dependencies: get-func-name "^2.0.2" -chokidar@3.5.3: - version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -1895,10 +1915,10 @@ chokidar@3.5.3: optionalDependencies: fsevents "~2.3.2" -chokidar@^3.5.2: - version "3.6.0" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -1976,16 +1996,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-string@^1.6.0: version "1.9.1" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" @@ -2030,16 +2050,16 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^2.20.3: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@11.0.0: version "11.0.0" resolved "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz" integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== -commander@^2.20.3: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" @@ -2136,7 +2156,7 @@ crypto-js@^4.0.0, crypto-js@^4.1.1: currently-unhandled@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + resolved "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz" integrity sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng== dependencies: array-find-index "^1.0.1" @@ -2153,7 +2173,7 @@ dateformat@^4.5.1: dateformat@~1.0.4-1.2.3: version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" + resolved "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz" integrity sha512-5sFRfAAmbHdIts+eKjR9kYJoF0ViCMVX9yqLu5A7S/v+nd077KgCITOMiirmyCBiZpKLDXbBOkYm6tu7rX/TKg== dependencies: get-stdin "^4.0.1" @@ -2161,9 +2181,65 @@ dateformat@~1.0.4-1.2.3: debounce@^1.0.0: version "1.2.1" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== +debug@^3.2.7, debug@3.x: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.1.1: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.3.1: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.3.2: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.3.4: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@~4.3.1: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +debug@~4.3.2: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -2185,23 +2261,9 @@ debug@4.3.4: dependencies: ms "2.1.2" -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: - version "4.3.6" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== - dependencies: - ms "2.1.2" - debuggler@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/debuggler/-/debuggler-1.0.0.tgz#fd684145e2ecbb528cfc747fcdf201a17ff683e1" + resolved "https://registry.npmjs.org/debuggler/-/debuggler-1.0.0.tgz" integrity sha512-nq77mTAwWb0mXfhacMP0mQK4qlsTbNYgqM2gzdUR8zT9x7H8eV/GTklxljKnuBkKAdoLsrghTMq/UcrSzV0Etw== dependencies: caller-path "^2.0.0" @@ -2209,7 +2271,7 @@ debuggler@^1.0.0: decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decamelize@^4.0.0: @@ -2226,7 +2288,7 @@ deep-eql@^4.1.3: deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== default-require-extensions@^3.0.0: @@ -2247,7 +2309,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: define-properties@^1.1.3: version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: define-data-property "^1.0.1" @@ -2256,7 +2318,7 @@ define-properties@^1.1.3: delay@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + resolved "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== delayed-stream@~1.0.0: @@ -2276,14 +2338,9 @@ destroy@1.2.0: detect-libc@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - diff@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" @@ -2294,6 +2351,11 @@ diff@^5.0.0, diff@^5.2.0: resolved "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +diff@5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -2303,7 +2365,7 @@ dir-glob@^3.0.1: doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" @@ -2315,14 +2377,14 @@ dotenv@^8.2.0: dprint-node@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/dprint-node/-/dprint-node-1.0.8.tgz#a02470722d8208a7d7eb3704328afda1d6758625" + resolved "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz" integrity sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg== dependencies: detect-libc "^1.0.3" dynamic-dedupe@^0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" + resolved "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz" integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== dependencies: xtend "^4.0.0" @@ -2398,7 +2460,7 @@ err-code@^3.0.1: error-ex@^1.2.0: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" @@ -2422,12 +2484,12 @@ es6-error@^4.0.1: es6-promise@^4.0.3: version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + resolved "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== es6-promisify@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + resolved "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz" integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== dependencies: es6-promise "^4.0.3" @@ -2437,21 +2499,21 @@ escalade@^3.1.1, escalade@^3.1.2: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== -escape-html@1.0.3, escape-html@^1.0.3, escape-html@~1.0.3: +escape-html@^1.0.3, escape-html@~1.0.3, escape-html@1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escape-string-regexp@^4.0.0, escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + eslint-config-prettier@^8.7.0: version "8.10.0" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz" @@ -2502,7 +2564,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.7.0: +eslint@*, "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.0.0, eslint@^8.7.0, eslint@>=5.0.0, eslint@>=7.0.0: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -2557,7 +2619,7 @@ espree@^9.6.0, espree@^9.6.1: esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: @@ -2576,7 +2638,7 @@ esrecurse@^4.3.0: estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: @@ -2604,9 +2666,9 @@ ethereum-cryptography@^2.0.0: "@scure/bip32" "1.4.0" "@scure/bip39" "1.3.0" -ethers@^5.7.2: +"ethers@^5.0.0 || ^6.0.0", ethers@^5.7.2: version "5.7.2" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + resolved "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== dependencies: "@ethersproject/abi" "5.7.0" @@ -2642,7 +2704,7 @@ ethers@^5.7.2: ethjs-util@^0.1.6: version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" + resolved "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz" integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== dependencies: is-hex-prefixed "1.0.0" @@ -2717,7 +2779,7 @@ express@^4.19.2: eyes@^0.1.8: version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + resolved "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz" integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: @@ -2748,12 +2810,12 @@ fast-json-stable-stringify@^2.0.0: fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-stable-stringify@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" + resolved "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz" integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== fastq@^1.6.0: @@ -2770,7 +2832,7 @@ fecha@^4.2.0: fetch-cookie@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-3.0.1.tgz#6a77f7495e1a639ae019db916a234db8c85d5963" + resolved "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-3.0.1.tgz" integrity sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q== dependencies: set-cookie-parser "^2.4.8" @@ -2785,12 +2847,12 @@ file-entry-cache@^6.0.1: file-uri-to-path@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== filewatcher@~3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/filewatcher/-/filewatcher-3.0.1.tgz#f4a1957355ddaf443ccd78a895f3d55e23c8a034" + resolved "https://registry.npmjs.org/filewatcher/-/filewatcher-3.0.1.tgz" integrity sha512-Fro8py2B8EJupSP37Kyd4kjKZLr+5ksFq7Vbw8A392Z15Unq8016SPUDvO/AsDj5V6bbPk98PTAinpc5YhPbJw== dependencies: debounce "^1.0.0" @@ -2824,17 +2886,9 @@ find-cache-dir@^3.2.0: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@5.0.0, find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-up@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + resolved "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz" integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== dependencies: path-exists "^2.0.0" @@ -2842,7 +2896,7 @@ find-up@^1.0.0: find-up@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz" integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== dependencies: locate-path "^2.0.0" @@ -2855,6 +2909,22 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat-cache@^3.0.4: version "3.2.0" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz" @@ -2988,14 +3058,14 @@ get-package-type@^0.1.0: get-starknet-core@^4.0.0-next.3: version "4.0.0" - resolved "https://registry.yarnpkg.com/get-starknet-core/-/get-starknet-core-4.0.0.tgz#9a81101b3a4e54e090f76492b566abaa3b5865c7" + resolved "https://registry.npmjs.org/get-starknet-core/-/get-starknet-core-4.0.0.tgz" integrity sha512-6pLmidQZkC3wZsrHY99grQHoGpuuXqkbSP65F8ov1/JsEI8DDLkhsAuLCKFzNOK56cJp+f1bWWfTJ57e9r5eqQ== dependencies: "@starknet-io/types-js" "^0.7.7" get-stdin@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" integrity sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw== get-stream@^6.0.1: @@ -3017,27 +3087,27 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@7.2.0: - version "7.2.0" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== +glob@7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.1.1" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" @@ -3067,7 +3137,7 @@ globby@^11.1.0: google-protobuf@^3.15.5: version "3.21.4" - resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9" + resolved "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz" integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ== gopd@^1.0.1: @@ -3079,7 +3149,7 @@ gopd@^1.0.1: graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== graphemer@^1.4.0: @@ -3099,7 +3169,7 @@ growl@1.10.5: growly@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz" integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== has-flag@^3.0.0: @@ -3129,7 +3199,7 @@ has-symbols@^1.0.3: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: +hash.js@^1.0.0, hash.js@^1.0.3, hash.js@1.1.7: version "1.1.7" resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -3173,7 +3243,7 @@ hmac-drbg@^1.0.1: hosted-git-info@^2.1.4: version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== html-escaper@^2.0.0: @@ -3199,7 +3269,7 @@ human-signals@^4.3.0: humanize-ms@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== dependencies: ms "^2.0.0" @@ -3209,23 +3279,28 @@ husky@^8.0.3: resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@^0.4.24, iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@1.1.13: - version "1.1.13" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ieee754@^1.1.4, ieee754@^1.2.1: +ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +ieee754@1.1.13: + version "1.1.13" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" @@ -3236,7 +3311,7 @@ ignore@^5.2.0: resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -immer@^10.0.2: +immer@^10.0.2, immer@>=9.0.6: version "10.1.1" resolved "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz" integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== @@ -3256,7 +3331,7 @@ imurmurhash@^0.1.4: indent-string@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz" integrity sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg== dependencies: repeating "^2.0.0" @@ -3274,7 +3349,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3286,7 +3361,7 @@ ipaddr.js@1.9.1: is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-arrayish@^0.3.1: @@ -3303,7 +3378,7 @@ is-binary-path@~2.1.0: is-core-module@^2.13.0: version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== dependencies: hasown "^2.0.2" @@ -3315,7 +3390,7 @@ is-extglob@^2.1.1: is-finite@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + resolved "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz" integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== is-fullwidth-code-point@^3.0.0: @@ -3385,7 +3460,7 @@ is-unicode-supported@^0.1.0: is-utf8@^0.2.0: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== is-windows@^1.0.2: @@ -3395,7 +3470,7 @@ is-windows@^1.0.2: is-wsl@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== isarray@^1.0.0, isarray@~1.0.0: @@ -3410,7 +3485,7 @@ isexe@^2.0.0: isomorphic-fetch@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" + resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz" integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== dependencies: node-fetch "^2.6.1" @@ -3418,7 +3493,7 @@ isomorphic-fetch@^3.0.0: isomorphic-ws@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== isows@1.0.3: @@ -3488,19 +3563,19 @@ istanbul-reports@^3.0.2: jayson@^4.1.1: version "4.1.2" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" + resolved "https://registry.npmjs.org/jayson/-/jayson-4.1.2.tgz" integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA== dependencies: "@types/connect" "^3.4.33" "@types/node" "^12.12.54" "@types/ws" "^7.4.4" - JSONStream "^1.3.5" commander "^2.20.3" delay "^5.0.0" es6-promisify "^5.0.0" eyes "^0.1.8" isomorphic-ws "^4.0.1" json-stringify-safe "^5.0.1" + JSONStream "^1.3.5" uuid "^8.3.2" ws "^7.5.10" @@ -3509,7 +3584,7 @@ jmespath@0.15.0: resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz" integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w== -joi@17.x.x, joi@^17.9.2: +joi@^17.9.2, joi@17.x.x: version "17.13.3" resolved "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz" integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== @@ -3535,13 +3610,6 @@ js-sha3@0.8.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0, js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" @@ -3550,7 +3618,14 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^2.5.1: +js-yaml@^4.1.0, js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== @@ -3572,7 +3647,7 @@ json-stable-stringify-without-jsonify@^1.0.1: json-stringify-safe@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^2.2.2, json5@^2.2.3: @@ -3591,9 +3666,17 @@ jsonfile@^6.0.1: jsonparse@^1.2.0: version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + just-extend@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz" @@ -3601,7 +3684,7 @@ just-extend@^6.2.0: keyv@^4.5.3: version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: json-buffer "3.0.1" @@ -3668,7 +3751,7 @@ livepeer@^2.5.8: load-json-file@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz" integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== dependencies: graceful-fs "^4.1.2" @@ -3679,7 +3762,7 @@ load-json-file@^1.0.0: locate-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== dependencies: p-locate "^2.0.0" @@ -3786,7 +3869,7 @@ lodash.uniqby@4.5.0: lodash@4.17.x: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@4.1.0: @@ -3825,21 +3908,21 @@ long-timeout@0.1.1: resolved "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz" integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== -loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" lossless-json@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-4.0.2.tgz#f00c52815805d1421930a87e2670e27350958a3f" + resolved "https://registry.npmjs.org/lossless-json/-/lossless-json-4.0.2.tgz" integrity sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA== loud-rejection@^1.0.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + resolved "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz" integrity sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ== dependencies: currently-unhandled "^0.4.1" @@ -3859,14 +3942,14 @@ lru-cache@^10.1.0: lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" @@ -3885,7 +3968,7 @@ make-error@^1.1.1: map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== media-typer@0.3.0: @@ -3900,7 +3983,7 @@ memory-pager@^1.0.2: meow@^3.3.0: version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + resolved "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz" integrity sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA== dependencies: camelcase-keys "^2.0.0" @@ -3939,14 +4022,6 @@ micro-ftch@^0.3.1: resolved "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz" integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== -micromatch@4.0.5: - version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - micromatch@^4.0.4: version "4.0.7" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz" @@ -3955,6 +4030,14 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" +micromatch@4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" @@ -3992,13 +4075,6 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@4.2.1: - version "4.2.1" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz" - integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== - dependencies: - brace-expansion "^1.1.7" - minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" @@ -4006,19 +4082,26 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@4.2.1: + version "4.2.1" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz" + integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== + dependencies: + brace-expansion "^1.1.7" + minimist@^1.1.3, minimist@^1.2.6: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mkdirp@^0.5.1: version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: minimist "^1.2.6" -mocha@^9.1.3: +mocha@^9.1.3, mocha@>=7: version "9.2.2" resolved "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz" integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== @@ -4111,6 +4194,16 @@ mongodb@*: bson "^6.7.0" mongodb-connection-string-url "^3.0.0" +ms@^2.0.0, ms@^2.1.1, ms@^2.1.3, ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +ms@^3.0.0-canary.1: + version "3.0.0-canary.1" + resolved "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz" + integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" @@ -4121,22 +4214,12 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -ms@^3.0.0-canary.1: - version "3.0.0-canary.1" - resolved "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz" - integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== - multiformats@9.9.0: version "9.9.0" - resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + resolved "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz" integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== -mysql@2.18.1, mysql@^2.16.0, mysql@^2.17.1: +mysql@^2.16.0, mysql@^2.17.1, mysql@2.18.1: version "2.18.1" resolved "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz" integrity sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig== @@ -4179,19 +4262,19 @@ nise@^5.1.9: node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.7.0: version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" node-gyp-build@^4.3.0: version "4.8.2" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.2.tgz#4f802b71c1ab2ca16af830e6c1ea7dd1ad9496fa" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz" integrity sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw== node-notifier@^5.4.0: version "5.4.5" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.5.tgz#0cbc1a2b0f658493b4025775a13ad938e96091ef" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz" integrity sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ== dependencies: growly "^1.3.0" @@ -4239,7 +4322,7 @@ nodemon@^2.0.1: normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" @@ -4259,7 +4342,7 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" -nyc@^15.1.0: +nyc@^15.1.0, nyc@>=15: version "15.1.0" resolved "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz" integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== @@ -4294,7 +4377,7 @@ nyc@^15.1.0: object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-hash@^3.0.0: @@ -4373,7 +4456,7 @@ optionator@^0.9.3: p-limit@^1.1.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: p-try "^1.0.0" @@ -4394,7 +4477,7 @@ p-limit@^3.0.2: p-locate@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz" integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== dependencies: p-limit "^1.1.0" @@ -4422,7 +4505,7 @@ p-map@^3.0.0: p-try@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz" integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== p-try@^2.0.0: @@ -4442,12 +4525,12 @@ package-hash@^4.0.0: packet-reader@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz" integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== pako@^2.0.4: version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== parent-module@^1.0.0: @@ -4459,7 +4542,7 @@ parent-module@^1.0.0: parse-json@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz" integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== dependencies: error-ex "^1.2.0" @@ -4471,14 +4554,14 @@ parseurl@~1.3.3: path-exists@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz" integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== dependencies: pinkie-promise "^2.0.0" path-exists@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-exists@^4.0.0: @@ -4503,22 +4586,22 @@ path-key@^4.0.0: path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - path-to-regexp@^6.2.1: version "6.2.2" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz" integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + path-type@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== dependencies: graceful-fs "^4.1.2" @@ -4537,27 +4620,27 @@ pathval@^1.1.1: pg-connection-string@^2.5.0: version "2.7.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" + resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz" integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== pg-int8@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== pg-minify@1.6.2: version "1.6.2" - resolved "https://registry.yarnpkg.com/pg-minify/-/pg-minify-1.6.2.tgz#055acfe862cfca3ca0a529020846b0f308d68e70" + resolved "https://registry.npmjs.org/pg-minify/-/pg-minify-1.6.2.tgz" integrity sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg== pg-pool@^3.5.2: version "3.7.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" + resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz" integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== pg-promise@^10.12.0: version "10.15.4" - resolved "https://registry.yarnpkg.com/pg-promise/-/pg-promise-10.15.4.tgz#b8b5055489f375a43e5d3edbff1d41ddb3817b2f" + resolved "https://registry.npmjs.org/pg-promise/-/pg-promise-10.15.4.tgz" integrity sha512-BKlHCMCdNUmF6gagVbehRWSEiVcZzPVltEx14OJExR9Iz9/1R6KETDWLLGv2l6yRqYFnEZZy1VDjRhArzeIGrw== dependencies: assert-options "0.8.0" @@ -4567,12 +4650,12 @@ pg-promise@^10.12.0: pg-protocol@^1.5.0: version "1.7.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" + resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz" integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== pg-types@^2.1.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== dependencies: pg-int8 "1.0.1" @@ -4581,9 +4664,9 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@8.8.0: +pg@>=8.0, pg@8.8.0: version "8.8.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.8.0.tgz#a77f41f9d9ede7009abfca54667c775a240da686" + resolved "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz" integrity sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw== dependencies: buffer-writer "2.0.0" @@ -4596,7 +4679,7 @@ pg@8.8.0: pgpass@1.x: version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz" integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== dependencies: split2 "^4.1.0" @@ -4618,19 +4701,19 @@ pidtree@0.6.0: pify@^2.0.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pinkie-promise@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== pkg-dir@^4.1.0: @@ -4642,29 +4725,29 @@ pkg-dir@^4.1.0: pkg-up@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz" integrity sha512-fjAPuiws93rm7mPUu21RdBnkeZNrbfCFCwfAhPWY+rR3zG0ubpe5cEReHOw5fIbfmsxEV/g2kSxGTATY3Bpnwg== dependencies: find-up "^2.1.0" postgres-array@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== postgres-bytea@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz" integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== postgres-date@~1.0.4: version "1.0.7" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== postgres-interval@^1.1.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz" integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== dependencies: xtend "^4.0.0" @@ -4681,7 +4764,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.8.4: +prettier@^2.8.4, prettier@>=1.13.0: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -4726,7 +4809,7 @@ proxy-addr@~2.0.7: psl@^1.1.33: version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== pstree.remy@^1.1.8: @@ -4734,16 +4817,16 @@ pstree.remy@^1.1.8: resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + punycode@1.3.2: version "1.3.2" resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== -punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - qs@6.11.0: version "6.11.0" resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" @@ -4793,9 +4876,16 @@ react-is@^16.13.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +"react@^16.8.0 || ^17.0.0 || ^18.0.0", react@>=16.8, react@>=17.0.0: + version "18.3.1" + resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + read-pkg-up@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz" integrity sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== dependencies: find-up "^1.0.0" @@ -4803,13 +4893,22 @@ read-pkg-up@^1.0.1: read-pkg@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz" integrity sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== dependencies: load-json-file "^1.0.0" normalize-package-data "^2.3.2" path-type "^1.0.0" +readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@2.3.7: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" @@ -4823,15 +4922,6 @@ readable-stream@2.3.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -4841,7 +4931,7 @@ readdirp@~3.6.0: redent@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + resolved "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz" integrity sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g== dependencies: indent-string "^2.1.0" @@ -4849,7 +4939,7 @@ redent@^1.0.0: redeyed@~2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + resolved "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz" integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== dependencies: esprima "~4.0.0" @@ -4873,7 +4963,7 @@ reflect-metadata@^0.1.12: regenerator-runtime@^0.14.0: version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== release-zalgo@^1.0.0: @@ -4885,7 +4975,7 @@ release-zalgo@^1.0.0: repeating@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + resolved "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" integrity sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A== dependencies: is-finite "^1.0.0" @@ -4917,7 +5007,7 @@ resolve-from@^5.0.0: resolve@^1.0.0, resolve@^1.10.0: version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: is-core-module "^2.13.0" @@ -4949,7 +5039,7 @@ rfdc@^1.3.0: rimraf@^2.6.1: version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" @@ -4963,7 +5053,7 @@ rimraf@^3.0.0, rimraf@^3.0.2: rpc-websockets@^9.0.2: version "9.0.4" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" + resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.0.4.tgz" integrity sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ== dependencies: "@swc/helpers" "^0.5.11" @@ -4984,44 +5074,49 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-stable-stringify@^2.3.1: version "2.4.3" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: +safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" - integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== - sax@>=0.6.0: version "1.4.1" resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz" integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== +sax@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" + integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== + scrypt-js@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.7.1: +semver@^5.5.0: + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^5.7.1: version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: @@ -5029,9 +5124,14 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.5.3: +semver@^7.3.7: + version "7.6.3" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +semver@^7.5.3: version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== semver@~7.0.0: @@ -5039,6 +5139,11 @@ semver@~7.0.0: resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +"semver@2 || 3 || 4 || 5": + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + send@0.18.0: version "0.18.0" resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" @@ -5082,7 +5187,7 @@ set-blocking@^2.0.0: set-cookie-parser@^2.4.8: version "2.7.0" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz#ef5552b56dc01baae102acb5fc9fb8cd060c30f9" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz" integrity sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ== set-function-length@^1.2.1: @@ -5116,7 +5221,7 @@ shebang-regex@^3.0.0: shellwords@^0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== side-channel@^1.0.4: @@ -5131,7 +5236,7 @@ side-channel@^1.0.4: signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== simple-peer@^9.11.1: @@ -5211,7 +5316,7 @@ sorted-array-functions@^1.0.0: source-map-support@^0.5.12: version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -5219,7 +5324,7 @@ source-map-support@^0.5.12: source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== sparse-bitfield@^3.0.3: @@ -5243,7 +5348,7 @@ spawn-wrap@^2.0.0: spdx-correct@^3.0.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz" integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" @@ -5251,12 +5356,12 @@ spdx-correct@^3.0.0: spdx-exceptions@^2.1.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz" integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== spdx-expression-parse@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" @@ -5264,17 +5369,17 @@ spdx-expression-parse@^3.0.0: spdx-license-ids@^3.0.0: version "3.0.20" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz#e44ed19ed318dd1e5888f93325cee800f0f51b89" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz" integrity sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw== spex@3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/spex/-/spex-3.2.0.tgz#fa4a21922407e112624977b445a6d634578a1127" + resolved "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz" integrity sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg== split2@^4.1.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== sprintf-js@~1.0.2: @@ -5292,9 +5397,14 @@ stack-trace@0.0.x: resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== +"starknet-types-07@npm:@starknet-io/types-js@^0.7.7": + version "0.7.7" + resolved "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.7.tgz" + integrity sha512-WLrpK7LIaIb8Ymxu6KF/6JkGW1sso988DweWu7p5QY/3y7waBIiPvzh27D9bX5KIJNRDyOoOVoHVEKYUYWZ/RQ== + starknet@^6.11.0: version "6.11.0" - resolved "https://registry.yarnpkg.com/starknet/-/starknet-6.11.0.tgz#5d7e868e913777e9bf64323e59ed8be86437f291" + resolved "https://registry.npmjs.org/starknet/-/starknet-6.11.0.tgz" integrity sha512-u50KrGDi9fbu1Ogu7ynwF/tSeFlp3mzOg1/Y5x50tYFICImo3OfY4lOz9OtYDk404HK4eUujKkhov9tG7GAKlg== dependencies: "@noble/curves" "~1.4.0" @@ -5316,6 +5426,20 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + string-argv@0.3.2: version "0.3.2" resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" @@ -5330,7 +5454,7 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.0, string-width@^5.0.1: +string-width@^5.0.0: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -5339,19 +5463,14 @@ string-width@^5.0.0, string-width@^5.0.1: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: - safe-buffer "~5.1.0" + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" @@ -5369,7 +5488,7 @@ strip-ansi@^7.0.1: strip-bom@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" integrity sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== dependencies: is-utf8 "^0.2.0" @@ -5398,34 +5517,34 @@ strip-hex-prefix@1.0.0: strip-indent@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz" integrity sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA== dependencies: get-stdin "^4.0.1" -strip-json-comments@3.1.1, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - strip-json-comments@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +strip-json-comments@^3.1.1, strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + superstruct@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" + resolved "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz" integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: - has-flag "^4.0.0" + has-flag "^3.0.0" -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -5439,9 +5558,16 @@ supports-color@^7, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tcomb-validation@^3.3.0: @@ -5467,7 +5593,7 @@ test-exclude@^6.0.0: text-encoding-utf-8@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + resolved "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz" integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== text-hex@1.0.x: @@ -5482,7 +5608,7 @@ text-table@^0.2.0: "through@>=2.2.7 <3": version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== to-fast-properties@^2.0.0: @@ -5509,7 +5635,7 @@ touch@^3.1.0: tough-cookie@^4.0.0: version "4.1.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz" integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== dependencies: psl "^1.1.33" @@ -5531,12 +5657,12 @@ tr46@~0.0.3: tree-kill@^1.2.1: version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== trim-newlines@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" integrity sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw== triple-beam@^1.3.0: @@ -5551,12 +5677,12 @@ ts-luxon@^4.3.2: ts-mixer@^6.0.3: version "6.0.4" - resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + resolved "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz" integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== ts-node-dev@1.0.0-pre.44: version "1.0.0-pre.44" - resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0-pre.44.tgz#2f4d666088481fb9c4e4f5bc8f15995bd8b06ecb" + resolved "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.0.0-pre.44.tgz" integrity sha512-M5ZwvB6FU3jtc70i5lFth86/6Qj5XR5nMMBwVxZF4cZhpO7XcbWw6tbNiJo22Zx0KfjEj9py5DANhwLOkPPufw== dependencies: dateformat "~1.0.4-1.2.3" @@ -5574,7 +5700,7 @@ ts-node-dev@1.0.0-pre.44: ts-node@*, ts-node@^10.9.1: version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -5593,21 +5719,21 @@ ts-node@*, ts-node@^10.9.1: ts-poet@^6.7.0: version "6.9.0" - resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-6.9.0.tgz#e63ac8d8a9e91a2e0e5d2bf0755db71346728bd2" + resolved "https://registry.npmjs.org/ts-poet/-/ts-poet-6.9.0.tgz" integrity sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ== dependencies: dprint-node "^1.0.8" ts-proto-descriptors@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz#3a0e301a91cc6e932ac8dd13f7e07caa8a032c43" + resolved "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz" integrity sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw== dependencies: "@bufbuild/protobuf" "^2.0.0" ts-proto@^2.0.2: version "2.2.3" - resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-2.2.3.tgz#693714e83e17141af2dd749b07a929a432930010" + resolved "https://registry.npmjs.org/ts-proto/-/ts-proto-2.2.3.tgz" integrity sha512-sI9qyTcMg7syJjk0CvkRVxZyAuCbpweO2Kq5Dz/JXaRwpaB9ujMHdFF06raXj5nLxDknzV5cUIcdCuUThPXuYg== dependencies: "@bufbuild/protobuf" "^2.0.0" @@ -5617,7 +5743,7 @@ ts-proto@^2.0.2: ts-protoc-gen@^0.15.0: version "0.15.0" - resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz#2fec5930b46def7dcc9fa73c060d770b7b076b7b" + resolved "https://registry.npmjs.org/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz" integrity sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g== dependencies: google-protobuf "^3.15.5" @@ -5633,7 +5759,7 @@ tsconfig-paths@^4.0.0: tsconfig@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + resolved "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz" integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== dependencies: "@types/strip-bom" "^3.0.0" @@ -5643,7 +5769,7 @@ tsconfig@^7.0.0: tslib@^1.8.1: version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.3.0: @@ -5653,7 +5779,7 @@ tslib@^2.3.0: tslib@^2.4.0: version "2.7.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== tsutils@^3.21.0: @@ -5678,7 +5804,7 @@ tus-js-client@^3.1.0: tweetnacl-util@^0.15.1: version "0.15.1" - resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + resolved "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== tweetnacl@^1.0.3: @@ -5693,16 +5819,16 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - type-detect@^4.0.0, type-detect@^4.0.8, type-detect@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" @@ -5710,7 +5836,7 @@ type-fest@^0.20.2: type-fest@^0.8.0: version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== type-fest@^1.0.2: @@ -5738,9 +5864,9 @@ typedi@^0.8.0: resolved "https://registry.npmjs.org/typedi/-/typedi-0.8.0.tgz" integrity sha512-/c7Bxnm6eh5kXx2I+mTuO+2OvoWni5+rXA3PhXwVWCtJRYmz3hMok5s1AKLzoDvNAZqj/Q/acGstN0ri5aQoOA== -typescript@^4.5.4: +typescript@^4.5.4, typescript@>=2.7, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=5.0.4: version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== undefsafe@^2.0.5: @@ -5755,7 +5881,7 @@ unique-names-generator@^4.7.1: universalify@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== universalify@^2.0.0: @@ -5763,7 +5889,7 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0, unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== @@ -5785,7 +5911,7 @@ uri-js@^4.2.2: url-join@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== url-parse@^1.5.3, url-parse@^1.5.7: @@ -5809,9 +5935,9 @@ use-sync-external-store@1.2.2: resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== -utf-8-validate@^5.0.2: +utf-8-validate@^5.0.2, utf-8-validate@>=5.0.2: version "5.0.10" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz" integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== dependencies: node-gyp-build "^4.3.0" @@ -5826,11 +5952,6 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@3.3.2: - version "3.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" @@ -5838,9 +5959,14 @@ uuid@^8.3.2: uuid@^9.0.0: version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" @@ -5848,7 +5974,7 @@ v8-compile-cache-lib@^3.0.1: validate-npm-package-license@^3.0.1: version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" @@ -5856,7 +5982,7 @@ validate-npm-package-license@^3.0.1: validator@^13.6.0: version "13.12.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" + resolved "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz" integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== vary@^1, vary@~1.1.2: @@ -5895,7 +6021,7 @@ webidl-conversions@^7.0.0: whatwg-fetch@^3.4.1: version "3.6.20" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz" integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== whatwg-url@^13.0.0: @@ -5919,20 +6045,20 @@ which-module@^2.0.0: resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which@2.0.2, which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - which@^1.3.0: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" +which@^2.0.1, which@2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + winston-transport@^4.7.0: version "4.7.1" resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz" @@ -5961,7 +6087,7 @@ winston@^3.2.1: word-wrap@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== workerpool@6.2.0: @@ -5987,7 +6113,16 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: +wrap-ansi@^8.0.1: + version "8.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== @@ -6011,24 +6146,14 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@7.4.6: - version "7.4.6" - resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== - -ws@8.13.0: - version "8.13.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== - -ws@^7.5.10: +ws@*, ws@^7.5.10: version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.5.0: version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@~8.17.1: @@ -6036,6 +6161,16 @@ ws@~8.17.1: resolved "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@7.4.6: + version "7.4.6" + resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +ws@8.13.0: + version "8.13.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + xml2js@0.4.19: version "0.4.19" resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz" @@ -6056,7 +6191,7 @@ xmlhttprequest-ssl@~2.0.0: xtend@^4.0.0: version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: @@ -6069,26 +6204,21 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" @@ -6099,7 +6229,7 @@ yargs-parser@^18.1.2: yargs-parser@^20.2.2: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^21.1.1: @@ -6107,6 +6237,11 @@ yargs-parser@^21.1.1: resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" @@ -6117,19 +6252,6 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yargs@^15.0.2: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" @@ -6149,7 +6271,7 @@ yargs@^15.0.2: yargs@^17.2.1, yargs@^17.7.2: version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -6160,6 +6282,19 @@ yargs@^17.2.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yn@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" From a91f3b5b952940f1ad4ea3bed881c52f11f83123 Mon Sep 17 00:00:00 2001 From: rozaso Date: Tue, 21 Jan 2025 10:14:53 -0800 Subject: [PATCH 02/10] Initial Client connections to VNodes --- docker/v1/.env | 4 +- docker/v10/.env | 1 - docker/v2/.env | 1 - docker/v3/.env | 1 - docker/v4/.env | 1 - docker/v5/.env | 1 - docker/v6/.env | 1 - docker/v7/.env | 1 - docker/v8/.env | 1 - docker/v9/.env | 1 - src/app.ts | 33 +-- src/services/WebSockets/BlockStatusManager.ts | 10 +- src/services/WebSockets/WebSocketClient.ts | 8 +- src/services/WebSockets/WebSocketManager.ts | 5 +- src/services/WebSockets/WebSocketServer.ts | 189 ++++++++++++++++-- 15 files changed, 201 insertions(+), 57 deletions(-) diff --git a/docker/v1/.env b/docker/v1/.env index 69161bc..ac9bced 100644 --- a/docker/v1/.env +++ b/docker/v1/.env @@ -1,6 +1,4 @@ -WS_PORT=8080 -WS_MAX_PAYLOAD=5242880 - +WS_MAX_PAYLOAD=5242880 // 5mb DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 DISCOVERY_REFRESH_INTERVAL=60000 diff --git a/docker/v10/.env b/docker/v10/.env index a82a393..82bf047 100644 --- a/docker/v10/.env +++ b/docker/v10/.env @@ -1,4 +1,3 @@ -WS_PORT=8090 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v2/.env b/docker/v2/.env index 073d595..82bf047 100644 --- a/docker/v2/.env +++ b/docker/v2/.env @@ -1,4 +1,3 @@ -WS_PORT=8081 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v3/.env b/docker/v3/.env index a4edc77..82bf047 100644 --- a/docker/v3/.env +++ b/docker/v3/.env @@ -1,4 +1,3 @@ -WS_PORT=8083 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v4/.env b/docker/v4/.env index 0e6b675..82bf047 100644 --- a/docker/v4/.env +++ b/docker/v4/.env @@ -1,4 +1,3 @@ -WS_PORT=8084 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v5/.env b/docker/v5/.env index 0eaa382..82bf047 100644 --- a/docker/v5/.env +++ b/docker/v5/.env @@ -1,4 +1,3 @@ -WS_PORT=8085 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v6/.env b/docker/v6/.env index ec9c9db..82bf047 100644 --- a/docker/v6/.env +++ b/docker/v6/.env @@ -1,4 +1,3 @@ -WS_PORT=8086 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v7/.env b/docker/v7/.env index b4d070d..82bf047 100644 --- a/docker/v7/.env +++ b/docker/v7/.env @@ -1,4 +1,3 @@ -WS_PORT=8087 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v8/.env b/docker/v8/.env index ab8e23a..82bf047 100644 --- a/docker/v8/.env +++ b/docker/v8/.env @@ -1,4 +1,3 @@ -WS_PORT=8088 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/docker/v9/.env b/docker/v9/.env index f1f59de..82bf047 100644 --- a/docker/v9/.env +++ b/docker/v9/.env @@ -1,4 +1,3 @@ -WS_PORT=8089 WS_MAX_PAYLOAD=5242880 DISCOVERY_MIN_ARCHIVE_NODES=1 DISCOVERY_MAX_CONNECTION_RETRIES=3 diff --git a/src/app.ts b/src/app.ts index 6abab47..9bfa3f5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -50,6 +50,12 @@ async function startServer(logLevel: string = null, testMode = false, padder = 0 await initValidator(); + const validatorContractState = Container.get(ValidatorContractState); + await validatorContractState.postConstruct(); + const validatorNode = Container.get(ValidatorNode); + const archivalNodes = validatorContractState.getArchivalNodesMap(); + + const app: Express = express(); server = http.createServer(app); @@ -84,11 +90,21 @@ async function startServer(logLevel: string = null, testMode = false, padder = 0 - 🛡️ Server listening on port: ${PORT} 🛡️ + 🛡️ HTTP Server listening on port: ${PORT} 🛡️ ################################################ `); }); + + + // Initialize WebSocket Manager with error handling + try { + const wsManager = Container.get(WebSocketManager); + await wsManager.postConstruct(validatorNode.nodeId, validatorContractState.wallet, archivalNodes, server); + } catch (error) { + log.error('Failed to initialize WebSocket Manager:', error); + log.warn('Continuing with HTTP server initialization despite WebSocket failure'); + } } function printMemoryUsage() { @@ -116,21 +132,6 @@ export async function initValidator() { const validatorNode = Container.get(ValidatorNode); await validatorNode.postConstruct(); - const validatorContractState = Container.get(ValidatorContractState); - await validatorContractState.postConstruct(); - const archivalNodes = validatorContractState.getArchivalNodesMap(); - - // Initialize WebSocket Manager with error handling - try { - const wsManager = Container.get(WebSocketManager); - await wsManager.postConstruct(validatorNode.nodeId, validatorContractState.wallet, archivalNodes); - log.info('WebSocket Manager initialized successfully'); - } catch (error) { - log.error('Failed to initialize WebSocket Manager:', error); - log.warn('Continuing with HTTP server initialization despite WebSocket failure'); - } - - const validatorRpc = Container.get(ValidatorRpc); Check.notNull(validatorRpc, 'ValidatorRpc is null'); diff --git a/src/services/WebSockets/BlockStatusManager.ts b/src/services/WebSockets/BlockStatusManager.ts index 62c67e8..38f2698 100644 --- a/src/services/WebSockets/BlockStatusManager.ts +++ b/src/services/WebSockets/BlockStatusManager.ts @@ -28,17 +28,17 @@ export class BlockStatusManager { } confirmation.nodes.add(nodeId); - this.log.debug(`Block ${blockHash} confirmed by ${nodeId}. Total confirmations: ${confirmation.nodes.size}`); + this.log.debug(`Block ${blockHash} confirmed by ANode: ${nodeId}. Total confirmations: ${confirmation.nodes.size}`); if (confirmation.nodes.size >= this.REQUIRED_CONFIRMATIONS) { - await this.handleBlockConfirmed(blockData); + await this.handleBlockConfirmed(blockHash, blockData); } } - private async handleBlockConfirmed(blockData: BlockData) { - this.log.info(`Block ${blockData.hash} reached required confirmations`); + private async handleBlockConfirmed(blockHash: string, blockData: BlockData) { + this.log.info(`Block ${blockHash} reached required confirmations`); this.wsServer.broadcastBlockUpdate(blockData); - this.confirmations.delete(blockData.hash); + this.confirmations.delete(blockHash); } private cleanupOldConfirmations() { diff --git a/src/services/WebSockets/WebSocketClient.ts b/src/services/WebSockets/WebSocketClient.ts index ff12fa5..31b28f3 100644 --- a/src/services/WebSockets/WebSocketClient.ts +++ b/src/services/WebSockets/WebSocketClient.ts @@ -54,7 +54,6 @@ export class WebSocketClient { async postConstruct(vNodeId: string, wallet: Wallet) { try { - this.log.info('WebSocket client postConstruct started'); this.vNodeId = vNodeId; this.wallet = wallet; await this.initializeConnections(); @@ -186,12 +185,13 @@ export class WebSocketClient { } private handleArchiveMessage(nodeId: string, message: WSMessage) { - console.log(`Received message from ${nodeId}:`, message); - this.log.debug(`Received message from ${nodeId}:`, message); + console.log('****** handleArchiveMessag ****************************************************************************************************************************************************************', nodeId); + console.log(`Received message from ANode: ${nodeId}:`, message); + this.log.debug(`Received message from ANode: ${nodeId}:`, message); if (message.type === 'BLOCK_STORED' && message.data) { this.blockManager.handleBlockConfirmation( - message.data.hash, + message.data.block_hash, nodeId, message.data ); diff --git a/src/services/WebSockets/WebSocketManager.ts b/src/services/WebSockets/WebSocketManager.ts index 37b62ec..e8a9370 100644 --- a/src/services/WebSockets/WebSocketManager.ts +++ b/src/services/WebSockets/WebSocketManager.ts @@ -7,6 +7,7 @@ import { DiscoveryService } from './DiscoverService'; import { BlockStatusManager } from './BlockStatusManager'; import { NodeInfo } from '../messaging-common/validatorContractState'; import { Wallet } from 'ethers' +import { Server } from 'http'; @Service() export class WebSocketManager { @@ -20,14 +21,14 @@ export class WebSocketManager { private readonly discoveryService: DiscoveryService ) {} - async postConstruct(vNodeId: string, wallet: Wallet, archivalNodes: Map) { + async postConstruct(vNodeId: string, wallet: Wallet, archivalNodes: Map, server: Server) { try { if (!archivalNodes) { throw new Error('archivalNodes is undefined'); } await this.discoveryService.initialize(vNodeId, archivalNodes); - await this.wsServer.postConstruct(); + await this.wsServer.postConstruct(server); await this.wsClient.postConstruct(vNodeId, wallet); // Store interval reference diff --git a/src/services/WebSockets/WebSocketServer.ts b/src/services/WebSockets/WebSocketServer.ts index 79dc242..6bd3511 100644 --- a/src/services/WebSockets/WebSocketServer.ts +++ b/src/services/WebSockets/WebSocketServer.ts @@ -4,11 +4,14 @@ import WebSocket from 'ws'; import { WinstonUtil } from "../../utilz/winstonUtil"; import { EnvLoader } from "../../utilz/envLoader"; import { ClientInfo, BlockData, WSMessage } from './types'; +import { Server } from 'http'; interface WSClientConnection { ws: WebSocket; subscriptions: Set; connectedAt: number; + clientInfo?: ClientInfo; + reconnectAttempts?: number; } @Service() @@ -17,22 +20,35 @@ export class WebSocketServer { private clients = new Map(); private readonly log = WinstonUtil.newLog("WebSocketServer"); - async postConstruct() { + async postConstruct(server: Server) { try { - await this.initialize(); - this.log.info('WebSocket server initialized'); + await this.initialize(server); } catch (error) { this.log.error('Failed to initialize WebSocket server:', error); throw error; } } - private async initialize() { - const port = EnvLoader.getPropertyAsNumber('WS_PORT', 8080); - + private async initialize(server: Server) { this.wss = new WebSocket.Server({ - port, - maxPayload: EnvLoader.getPropertyAsNumber('WS_MAX_PAYLOAD', 5 * 1024 * 1024) + server, // server is the http server + maxPayload: EnvLoader.getPropertyAsNumber('WS_MAX_PAYLOAD', 5 * 1024 * 1024), + // Add CORS headers for browser clients + verifyClient: (info, cb) => { + const origin = info.origin || info.req.headers.origin; + // Allow Postman and localhost for testing + const allowedOrigins = ['http://localhost:3000', 'https://yourdomain.com']; + + // During testing, accept all connections + cb(true); + + // In production, use this: + // if (!origin || allowedOrigins.includes(origin)) { + // cb(true); + // } else { + // cb(false, 403, 'Forbidden'); + // } + } }); this.wss.on('connection', (ws: WebSocket) => { @@ -40,34 +56,71 @@ export class WebSocketServer { this.handleClientConnection(clientId, ws); }); - this.log.info(`WebSocket server listening on port ${port}`); + // Wait for server to be listening and get address + let addr = server.address(); + + if (!addr) { + this.log.info('Waiting for HTTP server to start listening...'); + await new Promise(resolve => server.once('listening', resolve)); + addr = server.address(); + } + + if (!addr) { + this.log.warn('Could not determine server address'); + return; + } + + const port = typeof addr === 'string' ? addr : addr?.port; + const host = typeof addr === 'string' ? addr : addr?.address === '::' ? 'localhost' : addr?.address; + this.log.info(`WebSocket server listening at ws://${host}:${port}`); } private handleClientConnection(clientId: string, ws: WebSocket) { this.log.info(`New client connected: ${clientId}`); - this.clients.set(clientId, { + + // Initialize client connection + const client: WSClientConnection = { ws, subscriptions: new Set(), - connectedAt: Date.now() - }); + connectedAt: Date.now(), + reconnectAttempts: 0 + }; + this.clients.set(clientId, client); + + // Set connection timeout for handshake + const handshakeTimeout = setTimeout(() => { + if (!client.clientInfo) { + this.log.warn(`Client ${clientId} failed to complete handshake within timeout`); + ws.close(1008, 'Handshake timeout'); + } + }, 5000); ws.on('message', (data: string) => { try { const message = JSON.parse(data) as WSMessage; + this.log.debug(`Received message from ${clientId}:`, message); this.handleClientMessage(clientId, message); } catch (error) { this.log.error(`Failed to parse message from client ${clientId}:`, error); + this.sendToClient(clientId, { + type: 'ERROR', + data: { message: 'Invalid message format' }, + timestamp: Date.now(), + nodeId: '', + nodeType: '', + events: [] + }); } }); - ws.on('close', () => { - this.log.info(`Client disconnected: ${clientId}`); - this.clients.delete(clientId); + ws.on('close', (code: number, reason: string) => { + clearTimeout(handshakeTimeout); + this.handleClientDisconnection(clientId, code, reason); }); ws.on('error', (error) => { this.log.error(`Client ${clientId} error:`, error); - this.clients.delete(clientId); + this.handleClientDisconnection(clientId, 1006, 'Connection error'); }); // Setup heartbeat @@ -78,17 +131,117 @@ export class WebSocketServer { }, 30000); ws.on('close', () => clearInterval(pingInterval)); + + // Send welcome message + this.sendToClient(clientId, { + type: 'WELCOME', + data: { clientId }, + timestamp: Date.now(), + nodeId: '', + nodeType: '', + events: [] + }); } private handleClientMessage(clientId: string, message: WSMessage) { + const client = this.clients.get(clientId); + if (!client) { + this.log.warn(`Message received from unknown client ${clientId}`); + return; + } + switch (message.type) { + case 'HANDSHAKE': + if (typeof message.data === 'object') { + client.clientInfo = message.data as ClientInfo; + client.reconnectAttempts = 0; + this.log.info(`Client ${clientId} handshake completed:`, client.clientInfo); + + this.sendToClient(clientId, { + type: 'HANDSHAKE_ACK', + data: { success: true, clientId }, + timestamp: Date.now(), + nodeId: '', + nodeType: '', + events: [] + }); + } + break; + case 'SUBSCRIBE': - const client = this.clients.get(clientId); - if (client && typeof message.data === 'string') { + if (typeof message.data === 'string') { client.subscriptions.add(message.data); this.log.debug(`Client ${clientId} subscribed to ${message.data}`); + + this.sendToClient(clientId, { + type: 'SUBSCRIBE_ACK', + data: { topic: message.data, success: true }, + timestamp: Date.now(), + nodeId: '', + nodeType: '', + events: [] + }); + } + break; + + case 'UNSUBSCRIBE': + if (typeof message.data === 'string') { + client.subscriptions.delete(message.data); + this.log.debug(`Client ${clientId} unsubscribed from ${message.data}`); + + this.sendToClient(clientId, { + type: 'UNSUBSCRIBE_ACK', + data: { topic: message.data, success: true }, + timestamp: Date.now(), + nodeId: '', + nodeType: '', + events: [] + }); } break; + + case 'PING': + this.sendToClient(clientId, { + type: 'PONG', + timestamp: Date.now(), + nodeId: '', + nodeType: '', + events: [] + }); + break; + + default: + this.log.warn(`Unknown message type received from client ${clientId}: ${message.type}`); + } + } + + private handleClientDisconnection(clientId: string, code: number, reason: string) { + const client = this.clients.get(clientId); + if (!client) return; + + this.log.info(`Client disconnected: ${clientId}, code: ${code}, reason: ${reason}`); + + // Keep client info for potential reconnection + if (code === 1001 || code === 1006) { // Normal closure or abnormal closure + setTimeout(() => { + if (this.clients.has(clientId)) { + this.clients.delete(clientId); + } + }, 5 * 60 * 1000); // Keep client info for 5 minutes + } else { + this.clients.delete(clientId); + } + } + + private sendToClient(clientId: string, message: WSMessage) { + const client = this.clients.get(clientId); + if (!client || client.ws.readyState !== WebSocket.OPEN) return; + + try { + client.ws.send(JSON.stringify(message)); + this.log.debug(`Sent message to client ${clientId}:`, message); + } catch (error) { + this.log.error(`Failed to send message to client ${clientId}:`, error); } } From 9fe686ba88e90844a4f0043a29079ff968adc773 Mon Sep 17 00:00:00 2001 From: rozaso Date: Thu, 23 Jan 2025 07:53:19 -0800 Subject: [PATCH 03/10] Push vNode websocket client connections. --- src/services/WebSockets/BlockStatusManager.ts | 15 +- src/services/WebSockets/DiscoverService.ts | 5 +- src/services/WebSockets/WebSocketClient.ts | 27 +- src/services/WebSockets/WebSocketServer.ts | 287 ++++++++++++++---- src/services/WebSockets/types.ts | 93 ++++-- 5 files changed, 310 insertions(+), 117 deletions(-) diff --git a/src/services/WebSockets/BlockStatusManager.ts b/src/services/WebSockets/BlockStatusManager.ts index 38f2698..de5953e 100644 --- a/src/services/WebSockets/BlockStatusManager.ts +++ b/src/services/WebSockets/BlockStatusManager.ts @@ -1,21 +1,18 @@ import { Service } from 'typedi'; import { WinstonUtil } from "../../utilz/winstonUtil"; -import { BlockData } from './types'; +import { BlockData, BlockConfirmation } from './types'; import { WebSocketServer } from './WebSocketServer'; -interface BlockConfirmation { - timestamp: number; - nodes: Set; -} - @Service() export class BlockStatusManager { private confirmations = new Map(); - private readonly REQUIRED_CONFIRMATIONS = 2; - private readonly CONFIRMATION_EXPIRY_TIME = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + private readonly REQUIRED_CONFIRMATIONS = 1; + private readonly CONFIRMATION_EXPIRY_TIME = 3 * 60 * 60 * 1000; // 3 hours in milliseconds private readonly log = WinstonUtil.newLog("BlockStatusManager"); - constructor(private readonly wsServer: WebSocketServer) {} + constructor( + private readonly wsServer: WebSocketServer + ) {} async handleBlockConfirmation(blockHash: string, nodeId: string, blockData: BlockData) { let confirmation = this.confirmations.get(blockHash); diff --git a/src/services/WebSockets/DiscoverService.ts b/src/services/WebSockets/DiscoverService.ts index a1df871..cb096c5 100644 --- a/src/services/WebSockets/DiscoverService.ts +++ b/src/services/WebSockets/DiscoverService.ts @@ -1,6 +1,6 @@ // src/services/DiscoveryService.ts import WebSocket from 'ws'; -import { ArchiveNodeInfo, DiscoveryConfig } from './types'; +import { DiscoveryConfig } from './types'; import { EnvLoader } from "../../utilz/envLoader"; import { NodeInfo } from '../messaging-common/validatorContractState'; import { WinstonUtil } from '../../utilz/winstonUtil'; @@ -126,7 +126,7 @@ export class DiscoveryService { ws.on('message', (data: Buffer) => { try { const message = JSON.parse(data.toString()); - this.log.debug(`Received message from ANode at ${wsUrl}:`, message); + this.log.info(`Received ${message.type} from ANode at ${wsUrl}:`, JSON.stringify(message, null, 2)); if (message.type === 'AUTH_CHALLENGE' && message.nonce) { const healthCheck = { @@ -222,7 +222,6 @@ export class DiscoveryService { } } - // Cleanup async destroy() { if (this.refreshInterval) { clearInterval(this.refreshInterval); diff --git a/src/services/WebSockets/WebSocketClient.ts b/src/services/WebSockets/WebSocketClient.ts index 31b28f3..e9d9e4f 100644 --- a/src/services/WebSockets/WebSocketClient.ts +++ b/src/services/WebSockets/WebSocketClient.ts @@ -1,29 +1,13 @@ import { Service } from 'typedi'; import WebSocket from 'ws'; import { WinstonUtil } from "../../utilz/winstonUtil"; -import { ArchiveNodeInfo, WSMessage } from './types'; +import { WSMessage, AuthResponse } from './types'; import { DiscoveryService } from './DiscoverService'; import { BlockStatusManager } from './BlockStatusManager'; -import crypto from 'crypto'; import { NodeInfo } from '../messaging-common/validatorContractState'; import { BitUtil } from '../../utilz/bitUtil'; import { EthUtil } from '../../utilz/ethUtil'; import { Wallet } from 'ethers'; -interface AuthChallenge { - type: 'AUTH_CHALLENGE'; - nonce: string; -} - -interface AuthResponse { - type: 'AUTH_RESPONSE'; - nonce: string; - signature: string; - validatorAddress: string; -} - -interface AuthSuccess { - type: 'AUTH_SUCCESS'; -} @Service() export class WebSocketClient { @@ -167,10 +151,9 @@ export class WebSocketClient { } private subscribeToEvents(ws: WebSocket, nodeId: string) { - const subscribeMessage: WSMessage = { + const subscribeMessage = { type: 'SUBSCRIBE', - nodeId: this.vNodeId, // Use validator's address - sourceNodeId: nodeId, + nodeId: this.vNodeId, nodeType: 'VALIDATOR', events: ['BLOCK_STORED'] }; @@ -185,9 +168,7 @@ export class WebSocketClient { } private handleArchiveMessage(nodeId: string, message: WSMessage) { - console.log('****** handleArchiveMessag ****************************************************************************************************************************************************************', nodeId); - console.log(`Received message from ANode: ${nodeId}:`, message); - this.log.debug(`Received message from ANode: ${nodeId}:`, message); + this.log.debug(`Received ${message.type} from ANode: ${nodeId}:`, JSON.stringify(message, null, 2)); if (message.type === 'BLOCK_STORED' && message.data) { this.blockManager.handleBlockConfirmation( diff --git a/src/services/WebSockets/WebSocketServer.ts b/src/services/WebSockets/WebSocketServer.ts index 6bd3511..3377ae4 100644 --- a/src/services/WebSockets/WebSocketServer.ts +++ b/src/services/WebSockets/WebSocketServer.ts @@ -1,19 +1,10 @@ -// src/services/WebSockets/WebSocketServer.ts import { Service } from 'typedi'; import WebSocket from 'ws'; import { WinstonUtil } from "../../utilz/winstonUtil"; import { EnvLoader } from "../../utilz/envLoader"; -import { ClientInfo, BlockData, WSMessage } from './types'; +import { ClientInfo, BlockData, WSMessage, WSClientConnection, SubscriptionFilter, Subscription, SubscribeMessage } from './types'; import { Server } from 'http'; -interface WSClientConnection { - ws: WebSocket; - subscriptions: Set; - connectedAt: number; - clientInfo?: ClientInfo; - reconnectAttempts?: number; -} - @Service() export class WebSocketServer { private wss: WebSocket.Server; @@ -37,7 +28,7 @@ export class WebSocketServer { verifyClient: (info, cb) => { const origin = info.origin || info.req.headers.origin; // Allow Postman and localhost for testing - const allowedOrigins = ['http://localhost:3000', 'https://yourdomain.com']; + // const allowedOrigins = ['http://localhost:*']; // During testing, accept all connections cb(true); @@ -81,7 +72,7 @@ export class WebSocketServer { // Initialize client connection const client: WSClientConnection = { ws, - subscriptions: new Set(), + subscriptions: new Map(), connectedAt: Date.now(), reconnectAttempts: 0 }; @@ -93,12 +84,12 @@ export class WebSocketServer { this.log.warn(`Client ${clientId} failed to complete handshake within timeout`); ws.close(1008, 'Handshake timeout'); } - }, 5000); + }, 3000000); ws.on('message', (data: string) => { try { const message = JSON.parse(data) as WSMessage; - this.log.debug(`Received message from ${clientId}:`, message); + this.log.debug(`Received ${message.type} from ${clientId}`); this.handleClientMessage(clientId, message); } catch (error) { this.log.error(`Failed to parse message from client ${clientId}:`, error); @@ -106,9 +97,6 @@ export class WebSocketServer { type: 'ERROR', data: { message: 'Invalid message format' }, timestamp: Date.now(), - nodeId: '', - nodeType: '', - events: [] }); } }); @@ -128,7 +116,7 @@ export class WebSocketServer { if (ws.readyState === WebSocket.OPEN) { ws.ping(); } - }, 30000); + }, 3000000); ws.on('close', () => clearInterval(pingInterval)); @@ -137,9 +125,6 @@ export class WebSocketServer { type: 'WELCOME', data: { clientId }, timestamp: Date.now(), - nodeId: '', - nodeType: '', - events: [] }); } @@ -153,33 +138,99 @@ export class WebSocketServer { switch (message.type) { case 'HANDSHAKE': if (typeof message.data === 'object') { - client.clientInfo = message.data as ClientInfo; + const handshakeData = message.data as ClientInfo & { clientId?: string }; + + // Strict validation of clientId + if (!handshakeData.clientId || handshakeData.clientId !== clientId) { + this.log.warn(`Client sent invalid clientId in handshake. Expected: ${clientId}, Received: ${handshakeData.clientId}`); + this.sendToClient(clientId, { + type: 'HANDSHAKE_ACK', + data: { + success: false, + error: 'Invalid clientId', + expectedClientId: clientId, + receivedClientId: handshakeData.clientId + }, + timestamp: Date.now(), + }); + client.ws.close(1008, 'Invalid handshake clientId'); + return; + } + + // Only proceed if clientId matches + client.clientInfo = handshakeData; client.reconnectAttempts = 0; - this.log.info(`Client ${clientId} handshake completed:`, client.clientInfo); + this.log.info(`Client ${clientId} handshake completed successfully`); this.sendToClient(clientId, { type: 'HANDSHAKE_ACK', data: { success: true, clientId }, timestamp: Date.now(), - nodeId: '', - nodeType: '', - events: [] }); } break; case 'SUBSCRIBE': - if (typeof message.data === 'string') { - client.subscriptions.add(message.data); - this.log.debug(`Client ${clientId} subscribed to ${message.data}`); + if (typeof message.data === 'object') { + const subscribeMsg = message.data as SubscribeMessage; + + // Check for existing similar subscriptions + const hasSimilarSubscription = Array.from(client.subscriptions.values()).some( + existing => this.areFiltersEqual(existing.filters, subscribeMsg.filters) + ); + + if (hasSimilarSubscription) { + this.sendToClient(clientId, { + type: 'SUBSCRIBE_ERROR', + data: { message: 'Similar subscription already exists' }, + timestamp: Date.now(), + }); + return; + } + + // Rate limiting: Check last subscription time + const now = Date.now(); + if (!client.lastSubscribeTime) { + client.lastSubscribeTime = now; + } else if (now - client.lastSubscribeTime < 1000) { // 1 second minimum between subscriptions + this.sendToClient(clientId, { + type: 'SUBSCRIBE_ERROR', + data: { message: 'Please wait before creating another subscription' }, + timestamp: now, + }); + return; + } + client.lastSubscribeTime = now; + + // Validate subscription filters + if (!this.areValidFilters(subscribeMsg.filters)) { + this.sendToClient(clientId, { + type: 'SUBSCRIBE_ERROR', + data: { message: 'Invalid subscription filters' }, + timestamp: Date.now(), + }); + return; + } + + const subscriptionId = this.generateSubscriptionId(); + const subscription: Subscription = { + id: subscriptionId, + filters: subscribeMsg.filters + }; + + client.subscriptions.set(subscriptionId, subscription); + + this.log.debug(`Client ${clientId} created subscription ${subscriptionId} with filters:`, + JSON.stringify(subscribeMsg.filters)); this.sendToClient(clientId, { type: 'SUBSCRIBE_ACK', - data: { topic: message.data, success: true }, + data: { + subscriptionId, + filters: subscribeMsg.filters, + success: true + }, timestamp: Date.now(), - nodeId: '', - nodeType: '', - events: [] }); } break; @@ -193,9 +244,6 @@ export class WebSocketServer { type: 'UNSUBSCRIBE_ACK', data: { topic: message.data, success: true }, timestamp: Date.now(), - nodeId: '', - nodeType: '', - events: [] }); } break; @@ -204,9 +252,6 @@ export class WebSocketServer { this.sendToClient(clientId, { type: 'PONG', timestamp: Date.now(), - nodeId: '', - nodeType: '', - events: [] }); break; @@ -215,6 +260,30 @@ export class WebSocketServer { } } + private areValidFilters(filters: SubscriptionFilter[]): boolean { + if (!Array.isArray(filters) || filters.length === 0) return false; + + return filters.every(filter => { + switch (filter.type) { + case 'NEW_BLOCK': + return true; + case 'CATEGORY': + return typeof filter.value === 'string' && filter.value.length > 0; + case 'SELF': + return true; + case 'SENDERS': + case 'RECEIVERS': + return typeof filter.value === 'string' || Array.isArray(filter.value); + default: + return false; + } + }); + } + + private generateSubscriptionId(): string { + return `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + private handleClientDisconnection(clientId: string, code: number, reason: string) { const client = this.clients.get(clientId); if (!client) return; @@ -239,35 +308,125 @@ export class WebSocketServer { try { client.ws.send(JSON.stringify(message)); - this.log.debug(`Sent message to client ${clientId}:`, message); + this.log.debug(`Sent ${message.type} to client ${clientId}`); } catch (error) { - this.log.error(`Failed to send message to client ${clientId}:`, error); + this.log.error(`Failed to send ${message.type} to client ${clientId}:`, error); } } public broadcastBlockUpdate(blockData: BlockData) { - const message: WSMessage = { - type: 'BLOCK_STORED', - data: blockData, - timestamp: Date.now(), - nodeId: blockData.nodeId, - nodeType: blockData.nodeType, - events: [] - }; - this.clients.forEach((client, clientId) => { - if (client.ws.readyState === WebSocket.OPEN && - client.subscriptions.has('BLOCK_STORED')) { - try { - client.ws.send(JSON.stringify(message)); - this.log.debug(`Sent block update to client ${clientId}`); - } catch (error) { - this.log.error(`Failed to send to client ${clientId}:`, error); - } - } + if (client.ws.readyState !== WebSocket.OPEN) return; + // Check each subscription + client.subscriptions.forEach((subscription, subId) => { + // Send notification for each filter, including match status + subscription.filters.forEach(filter => { + try { + // Check if filter matches + let matches = false; + if (filter.type === 'NEW_BLOCK') { + matches = true; + } else if (filter.type === 'CATEGORY' && this.matchesCategory(blockData, filter)) { + matches = true; + } else if (filter.type === 'SELF' && this.matchesSelf(blockData, filter, clientId)) { + matches = true; + } else if (filter.type === 'SENDERS' && this.matchesSenderAddress(blockData, filter)) { + matches = true; + } else if (filter.type === 'RECEIVERS' && this.matchesReceiversAddress(blockData, filter)) { + matches = true; + } + + if (matches) { + const updateMessage: WSMessage = { + type: filter.type, + data: { + block: blockData, + subscriptionId: subId, + matchedFilter: filter, + matches + }, + timestamp: Date.now(), + }; + + client.ws.send(JSON.stringify(updateMessage)); + this.log.debug(`Sent ${filter.type} update to client ${clientId} (subscription: ${subId}, matches: ${matches})`); + } + } catch (error) { + this.log.error(`Failed to process ${filter.type} for client ${clientId}:`, error); + } + }); + }); }); } + private matchesCategory(blockData: any, filter: SubscriptionFilter): boolean { + // Check if blockData and required fields exist + if (!blockData?.data_as_json?.txobjList?.[0]?.tx?.category) { + return false; + } + + // Get the category from the first transaction + const txCategory = blockData.data_as_json.txobjList[0].tx.category; + + // Compare with filter value + return txCategory === filter.value; + } + + private matchesSelf(blockData: any, filter: SubscriptionFilter, clientId: string): boolean { + const client = this.clients.get(clientId); + if (!client?.clientInfo?.producerAddress) return false; + + const address = client.clientInfo.producerAddress.toLowerCase(); + + // For debugging, only log relevant info + this.log.debug('Matching self for client:', { + clientId, + producerAddress: address + }); + + // Get sender and recipients from the first transaction + const tx = blockData?.data_as_json?.txobjList?.[0]?.tx; + if (!tx) return false; + + const sender = tx.sender?.toLowerCase(); + const recipients = tx.recipientsList?.map((r: string) => r.toLowerCase()) || []; + + // Check if address matches sender or is in recipients list + return sender === address || recipients.includes(address); + } + + private matchesSenderAddress(blockData: any, filter: SubscriptionFilter): boolean { + if (!filter.value) return false; + const addresses = (Array.isArray(filter.value) ? filter.value : [filter.value]) + .map(addr => addr.toLowerCase()); + + // Get sender from the first transaction + const sender = blockData?.data_as_json?.txobjList?.[0]?.tx?.sender?.toLowerCase(); + if (!sender) return false; + + // Check if sender matches any of the filter addresses + return addresses.includes(sender); + } + + private matchesReceiversAddress(blockData: any, filter: SubscriptionFilter): boolean { + if (!filter.value) return false; + const addresses = (Array.isArray(filter.value) ? filter.value : [filter.value]) + .map(addr => addr.toLowerCase()); + + // Get recipients list from the first transaction + const recipients = blockData?.data_as_json?.txobjList?.[0]?.tx?.recipientsList?.map( + (r: string) => r.toLowerCase() + ) || []; + + // Check if any recipient matches any of the filter addresses + return addresses.some(address => recipients.includes(address)); + } + + private matchWildcard(str: string, pattern: string): boolean { + const regexPattern = pattern.replace(/\*/g, '.*'); + return new RegExp(`^${regexPattern}$`).test(str); + } + private generateClientId(): string { return `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; } @@ -282,4 +441,14 @@ export class WebSocketServer { await new Promise((resolve) => this.wss.close(() => resolve())); } } + + private areFiltersEqual(filters1: SubscriptionFilter[], filters2: SubscriptionFilter[]): boolean { + if (filters1.length !== filters2.length) return false; + + return filters1.every((filter1, index) => { + const filter2 = filters2[index]; + return filter1.type === filter2.type && + JSON.stringify(filter1.value) === JSON.stringify(filter2.value); + }); + } } \ No newline at end of file diff --git a/src/services/WebSockets/types.ts b/src/services/WebSockets/types.ts index 5e51063..9ed6fbe 100644 --- a/src/services/WebSockets/types.ts +++ b/src/services/WebSockets/types.ts @@ -1,48 +1,95 @@ import WebSocket from 'ws'; -// src/services/WebSockets/types.ts export interface BlockData { hash: string; number: number; timestamp: number; nodeId: string; nodeType: string; - // ... other block data +} + +export interface BlockConfirmation { + timestamp: number; + nodes: Set; +} + +export interface DiscoveryConfig { + refreshInterval: number; + healthCheckTimeout: number; + minArchiveNodes: number; + maxRetries: number; +} + +export interface AuthResponse { + type: 'AUTH_RESPONSE'; + nonce: string; + signature: string; + validatorAddress: string; } export interface WSMessage { type: string; - nodeId: string; // ID of the subscriber (vNode) - sourceNodeId?: string; // ID of the anode being subscribed to - nodeType: string; - events: string[]; data?: any; - timestamp?: number; + timestamp: number; } -export interface ArchiveNodeInfo { - id: string; - wsUrl: string; - status: 'ACTIVE' | 'INACTIVE'; - lastSeen?: number; +export interface ClientInfo { + clientId: string; + producerAddress: string; } -export interface ClientInfo { +export interface WSClientConnection { ws: WebSocket; - subscriptions: Set; + subscriptions: Map; // subscriptionId -> Subscription connectedAt: number; + clientInfo?: ClientInfo; + reconnectAttempts?: number; + lastSubscribeTime?: number; +} + +export type FilterType = 'NEW_BLOCK' | 'CATEGORY' | 'SELF' | 'SENDERS' | 'RECEIVERS'; + +export interface BaseFilter { + type: FilterType; +} + +export interface BlockStoredFilter extends BaseFilter { + type: 'NEW_BLOCK'; +} + +export interface CategoryFilter extends BaseFilter { + type: 'CATEGORY'; + value: string; // Required string for category name } -export interface ArchiveNodeInfo { +export interface SelfFilter extends BaseFilter { + type: 'SELF'; +} + +export interface SendersFilter extends BaseFilter { + type: 'SENDERS'; + value: string[]; // Required array of sender addresses +} + +export interface ReceiversFilter extends BaseFilter { + type: 'RECEIVERS'; + value: string[]; // Required array of receiver addresses +} + +export type SubscriptionFilter = { + type: FilterType; + value?: string | string[]; +}; + +export interface Subscription { id: string; - wsUrl: string; - status: 'ACTIVE' | 'INACTIVE'; - lastSeen?: number; + filters: SubscriptionFilter[]; } -export interface DiscoveryConfig { - refreshInterval: number; - healthCheckTimeout: number; - minArchiveNodes: number; - maxRetries: number; +export interface SubscribeMessage { + filters: SubscriptionFilter[]; +} + +export interface Transaction { + producerAddress: string; } \ No newline at end of file From 416715022a25e5a6f3b2ee586be0c98450612769 Mon Sep 17 00:00:00 2001 From: rozaso Date: Mon, 27 Jan 2025 08:38:53 -0800 Subject: [PATCH 04/10] Handle Filtering along with wildcard. --- src/services/WebSockets/BlockStatusManager.ts | 6 +- src/services/WebSockets/WebSocketClient.ts | 22 +- src/services/WebSockets/WebSocketServer.ts | 602 ++++++++++-------- src/services/WebSockets/types.ts | 188 ++++-- 4 files changed, 476 insertions(+), 342 deletions(-) diff --git a/src/services/WebSockets/BlockStatusManager.ts b/src/services/WebSockets/BlockStatusManager.ts index de5953e..f20845c 100644 --- a/src/services/WebSockets/BlockStatusManager.ts +++ b/src/services/WebSockets/BlockStatusManager.ts @@ -1,6 +1,6 @@ import { Service } from 'typedi'; import { WinstonUtil } from "../../utilz/winstonUtil"; -import { BlockData, BlockConfirmation } from './types'; +import { FilterBlockResponse, BlockConfirmation } from './types'; import { WebSocketServer } from './WebSocketServer'; @Service() @@ -14,7 +14,7 @@ export class BlockStatusManager { private readonly wsServer: WebSocketServer ) {} - async handleBlockConfirmation(blockHash: string, nodeId: string, blockData: BlockData) { + async handleBlockConfirmation(blockHash: string, nodeId: string, blockData: FilterBlockResponse) { let confirmation = this.confirmations.get(blockHash); if (!confirmation) { confirmation = { @@ -32,7 +32,7 @@ export class BlockStatusManager { } } - private async handleBlockConfirmed(blockHash: string, blockData: BlockData) { + private async handleBlockConfirmed(blockHash: string, blockData: FilterBlockResponse) { this.log.info(`Block ${blockHash} reached required confirmations`); this.wsServer.broadcastBlockUpdate(blockData); this.confirmations.delete(blockHash); diff --git a/src/services/WebSockets/WebSocketClient.ts b/src/services/WebSockets/WebSocketClient.ts index e9d9e4f..9def3cf 100644 --- a/src/services/WebSockets/WebSocketClient.ts +++ b/src/services/WebSockets/WebSocketClient.ts @@ -1,7 +1,7 @@ import { Service } from 'typedi'; import WebSocket from 'ws'; import { WinstonUtil } from "../../utilz/winstonUtil"; -import { WSMessage, AuthResponse } from './types'; +import { WSMessage, AuthResponse, BlockReceivedMessage } from './types'; import { DiscoveryService } from './DiscoverService'; import { BlockStatusManager } from './BlockStatusManager'; import { NodeInfo } from '../messaging-common/validatorContractState'; @@ -155,7 +155,7 @@ export class WebSocketClient { type: 'SUBSCRIBE', nodeId: this.vNodeId, nodeType: 'VALIDATOR', - events: ['BLOCK_STORED'] + events: ['BLOCK'] }; try { @@ -168,14 +168,22 @@ export class WebSocketClient { } private handleArchiveMessage(nodeId: string, message: WSMessage) { - this.log.debug(`Received ${message.type} from ANode: ${nodeId}:`, JSON.stringify(message, null, 2)); - - if (message.type === 'BLOCK_STORED' && message.data) { + if (message.type === 'BLOCK') { + const blockData = (message as BlockReceivedMessage).data; + + if (!blockData?.blockHash ) { + this.log.warn(`Received invalid BLOCK message structure from ${nodeId}:`, message); + return; + } + + // blockData is already a FilterBlockResponse type with {blockHash, txs} this.blockManager.handleBlockConfirmation( - message.data.block_hash, + blockData.blockHash, nodeId, - message.data + blockData ); + } else { + this.log.warn(`Received unexpected message type from ${nodeId}:`, message); } } diff --git a/src/services/WebSockets/WebSocketServer.ts b/src/services/WebSockets/WebSocketServer.ts index 3377ae4..49a6aee 100644 --- a/src/services/WebSockets/WebSocketServer.ts +++ b/src/services/WebSockets/WebSocketServer.ts @@ -2,8 +2,24 @@ import { Service } from 'typedi'; import WebSocket from 'ws'; import { WinstonUtil } from "../../utilz/winstonUtil"; import { EnvLoader } from "../../utilz/envLoader"; -import { ClientInfo, BlockData, WSMessage, WSClientConnection, SubscriptionFilter, Subscription, SubscribeMessage } from './types'; import { Server } from 'http'; +import { + WSMessage, + WSClientConnection, + SubscriptionFilter, + Subscription, + FilterBlockResponse, + ClientInfo, + FilteredTxData, + BlockUpdateMessage +} from './types'; + +const CONSTANTS = { + HANDSHAKE_TIMEOUT: 30000, + HEARTBEAT_INTERVAL: 30000, + RECONNECT_WINDOW: 5 * 60 * 1000, + SUBSCRIPTION_RATE_LIMIT: 1000, +} as const; @Service() export class WebSocketServer { @@ -22,32 +38,27 @@ export class WebSocketServer { private async initialize(server: Server) { this.wss = new WebSocket.Server({ - server, // server is the http server + server, maxPayload: EnvLoader.getPropertyAsNumber('WS_MAX_PAYLOAD', 5 * 1024 * 1024), - // Add CORS headers for browser clients verifyClient: (info, cb) => { - const origin = info.origin || info.req.headers.origin; - // Allow Postman and localhost for testing - // const allowedOrigins = ['http://localhost:*']; - // During testing, accept all connections cb(true); - - // In production, use this: - // if (!origin || allowedOrigins.includes(origin)) { - // cb(true); - // } else { - // cb(false, 403, 'Forbidden'); - // } } }); + this.setupServerListeners(server); + } + + private async setupServerListeners(server: Server) { this.wss.on('connection', (ws: WebSocket) => { const clientId = this.generateClientId(); this.handleClientConnection(clientId, ws); }); - // Wait for server to be listening and get address + await this.waitForServerToListen(server); + } + + private async waitForServerToListen(server: Server) { let addr = server.address(); if (!addr) { @@ -67,9 +78,13 @@ export class WebSocketServer { } private handleClientConnection(clientId: string, ws: WebSocket) { - this.log.info(`New client connected: ${clientId}`); - - // Initialize client connection + const client = this.initializeClient(clientId, ws); + const cleanup = this.setupClientTimeouts(clientId, client); + this.setupClientListeners(clientId, ws, cleanup); + this.sendWelcomeMessage(clientId); + } + + private initializeClient(clientId: string, ws: WebSocket): WSClientConnection { const client: WSClientConnection = { ws, subscriptions: new Map(), @@ -77,15 +92,28 @@ export class WebSocketServer { reconnectAttempts: 0 }; this.clients.set(clientId, client); + this.log.info(`New client connected: ${clientId}`); + return client; + } - // Set connection timeout for handshake + private setupClientTimeouts(clientId: string, client: WSClientConnection) { const handshakeTimeout = setTimeout(() => { if (!client.clientInfo) { this.log.warn(`Client ${clientId} failed to complete handshake within timeout`); - ws.close(1008, 'Handshake timeout'); + client.ws.close(1008, 'Handshake timeout'); + } + }, CONSTANTS.HANDSHAKE_TIMEOUT); + + const pingInterval = setInterval(() => { + if (client.ws.readyState === WebSocket.OPEN) { + client.ws.ping(); } - }, 3000000); + }, CONSTANTS.HEARTBEAT_INTERVAL); + + return { handshakeTimeout, pingInterval }; + } + private setupClientListeners(clientId: string, ws: WebSocket, cleanup: { handshakeTimeout: NodeJS.Timeout, pingInterval: NodeJS.Timeout }) { ws.on('message', (data: string) => { try { const message = JSON.parse(data) as WSMessage; @@ -93,16 +121,13 @@ export class WebSocketServer { this.handleClientMessage(clientId, message); } catch (error) { this.log.error(`Failed to parse message from client ${clientId}:`, error); - this.sendToClient(clientId, { - type: 'ERROR', - data: { message: 'Invalid message format' }, - timestamp: Date.now(), - }); + this.sendErrorMessage(clientId); } }); ws.on('close', (code: number, reason: string) => { - clearTimeout(handshakeTimeout); + clearTimeout(cleanup.handshakeTimeout); + clearInterval(cleanup.pingInterval); this.handleClientDisconnection(clientId, code, reason); }); @@ -110,22 +135,6 @@ export class WebSocketServer { this.log.error(`Client ${clientId} error:`, error); this.handleClientDisconnection(clientId, 1006, 'Connection error'); }); - - // Setup heartbeat - const pingInterval = setInterval(() => { - if (ws.readyState === WebSocket.OPEN) { - ws.ping(); - } - }, 3000000); - - ws.on('close', () => clearInterval(pingInterval)); - - // Send welcome message - this.sendToClient(clientId, { - type: 'WELCOME', - data: { clientId }, - timestamp: Date.now(), - }); } private handleClientMessage(clientId: string, message: WSMessage) { @@ -137,166 +146,264 @@ export class WebSocketServer { switch (message.type) { case 'HANDSHAKE': - if (typeof message.data === 'object') { - const handshakeData = message.data as ClientInfo & { clientId?: string }; - - // Strict validation of clientId - if (!handshakeData.clientId || handshakeData.clientId !== clientId) { - this.log.warn(`Client sent invalid clientId in handshake. Expected: ${clientId}, Received: ${handshakeData.clientId}`); - this.sendToClient(clientId, { - type: 'HANDSHAKE_ACK', - data: { - success: false, - error: 'Invalid clientId', - expectedClientId: clientId, - receivedClientId: handshakeData.clientId - }, - timestamp: Date.now(), - }); - client.ws.close(1008, 'Invalid handshake clientId'); - return; - } - - // Only proceed if clientId matches - client.clientInfo = handshakeData; - client.reconnectAttempts = 0; - this.log.info(`Client ${clientId} handshake completed successfully`); - - this.sendToClient(clientId, { - type: 'HANDSHAKE_ACK', - data: { success: true, clientId }, - timestamp: Date.now(), - }); - } + this.handleHandshake(clientId, client, message); break; - case 'SUBSCRIBE': - if (typeof message.data === 'object') { - const subscribeMsg = message.data as SubscribeMessage; - - // Check for existing similar subscriptions - const hasSimilarSubscription = Array.from(client.subscriptions.values()).some( - existing => this.areFiltersEqual(existing.filters, subscribeMsg.filters) - ); - - if (hasSimilarSubscription) { - this.sendToClient(clientId, { - type: 'SUBSCRIBE_ERROR', - data: { message: 'Similar subscription already exists' }, - timestamp: Date.now(), - }); - return; - } - - // Rate limiting: Check last subscription time - const now = Date.now(); - if (!client.lastSubscribeTime) { - client.lastSubscribeTime = now; - } else if (now - client.lastSubscribeTime < 1000) { // 1 second minimum between subscriptions - this.sendToClient(clientId, { - type: 'SUBSCRIBE_ERROR', - data: { message: 'Please wait before creating another subscription' }, - timestamp: now, - }); - return; - } - client.lastSubscribeTime = now; - - // Validate subscription filters - if (!this.areValidFilters(subscribeMsg.filters)) { - this.sendToClient(clientId, { - type: 'SUBSCRIBE_ERROR', - data: { message: 'Invalid subscription filters' }, - timestamp: Date.now(), - }); - return; - } - - const subscriptionId = this.generateSubscriptionId(); - const subscription: Subscription = { - id: subscriptionId, - filters: subscribeMsg.filters - }; - - client.subscriptions.set(subscriptionId, subscription); - - this.log.debug(`Client ${clientId} created subscription ${subscriptionId} with filters:`, - JSON.stringify(subscribeMsg.filters)); - - this.sendToClient(clientId, { - type: 'SUBSCRIBE_ACK', - data: { - subscriptionId, - filters: subscribeMsg.filters, - success: true - }, - timestamp: Date.now(), - }); + if (!client.clientInfo) { + this.sendSubscribeError(clientId, 'Handshake required before subscribing'); + return; } + this.handleSubscribe(clientId, client, message); break; - case 'UNSUBSCRIBE': - if (typeof message.data === 'string') { - client.subscriptions.delete(message.data); - this.log.debug(`Client ${clientId} unsubscribed from ${message.data}`); - - this.sendToClient(clientId, { - type: 'UNSUBSCRIBE_ACK', - data: { topic: message.data, success: true }, - timestamp: Date.now(), - }); - } + this.handleUnsubscribe(clientId, client, message); break; - case 'PING': - this.sendToClient(clientId, { - type: 'PONG', - timestamp: Date.now(), - }); + this.sendPongMessage(clientId); break; - default: this.log.warn(`Unknown message type received from client ${clientId}: ${message.type}`); } } + private handleHandshake(clientId: string, client: WSClientConnection, message: WSMessage) { + if (message.type !== 'HANDSHAKE' || !this.isValidHandshakeData(message.data)) { + this.sendHandshakeError(clientId, 'Invalid handshake data'); + return; + } + + if (message.data.clientId !== clientId) { + this.sendHandshakeError(clientId, 'Invalid clientId'); + client.ws.close(1008, 'Invalid handshake clientId'); + return; + } + + client.clientInfo = message.data; + client.reconnectAttempts = 0; + this.log.info(`Client ${clientId} handshake completed successfully`); + + this.sendToClient(clientId, { + type: 'HANDSHAKE_ACK', + data: { success: true }, + timestamp: Date.now() + }); + } + + private handleSubscribe(clientId: string, client: WSClientConnection, message: WSMessage) { + if (message.type !== 'SUBSCRIBE') { + this.sendSubscribeError(clientId, 'Invalid subscription message'); + return; + } + + if (Array.isArray(message.filters)) { + if (message.filters.length === 1 && + 'type' in message.filters[0] && + message.filters[0].type === 'WILDCARD' && + message.filters[0].value[0] === '*') { + const subscriptionId = this.createSubscription(client, [{ type: 'WILDCARD', value: ['*'] }]); + this.sendSubscribeAck(clientId, subscriptionId, [{ type: 'WILDCARD', value: ['*'] }]); + return; + } else if (message.filters.some(filter => filter.type === 'WILDCARD' && filter.value[0] === '*')) { + this.sendSubscribeError(clientId, 'Wildcard must be the only filter'); + return; + } + } + + if (this.hasSimilarSubscription(client, message.filters)) { + this.sendSubscribeError(clientId, 'Similar subscription already exists'); + return; + } + + if (!this.checkSubscriptionRateLimit(client)) { + this.sendSubscribeError(clientId, 'Please wait before creating another subscription'); + return; + } + + if (!this.areValidFilters(message.filters)) { + this.sendSubscribeError(clientId, 'Invalid subscription filters'); + return; + } + + const subscriptionId = this.createSubscription(client, message.filters); + this.sendSubscribeAck(clientId, subscriptionId, message.filters); + } + + private handleUnsubscribe(clientId: string, client: WSClientConnection, message: WSMessage) { + if (message.type !== 'UNSUBSCRIBE' || !message.data?.subscriptionId) { + this.log.warn(`Invalid unsubscribe message from client ${clientId}`); + return; + } + + client.subscriptions.delete(message.data.subscriptionId); + this.log.debug(`Client ${clientId} unsubscribed from ${message.data.subscriptionId}`); + + this.sendToClient(clientId, { + type: 'UNSUBSCRIBE_ACK', + data: { success: true, subscriptionId: message.data.subscriptionId }, + timestamp: Date.now() + }); + } + + private isValidHandshakeData(data: any): data is ClientInfo { + return typeof data === 'object' + && 'clientId' in data + && typeof data.clientId === 'string' + } + + private hasSimilarSubscription(client: WSClientConnection, filters: SubscriptionFilter[]): boolean { + return Array.from(client.subscriptions.values()).some( + existing => this.areFiltersEqual(existing.filters, filters) + ); + } + + private checkSubscriptionRateLimit(client: WSClientConnection): boolean { + const now = Date.now(); + if (!client.lastSubscribeTime) { + client.lastSubscribeTime = now; + return true; + } + + if (now - client.lastSubscribeTime < CONSTANTS.SUBSCRIPTION_RATE_LIMIT) { + return false; + } + + client.lastSubscribeTime = now; + return true; + } + + private createSubscription(client: WSClientConnection, filters: SubscriptionFilter[]): string { + const subscriptionId = this.generateSubscriptionId(); + const subscription: Subscription = { + id: subscriptionId, + filters + }; + client.subscriptions.set(subscriptionId, subscription); + return subscriptionId; + } + + public broadcastBlockUpdate(blockData: FilterBlockResponse) { + this.clients.forEach((client, clientId) => { + if (client.ws.readyState !== WebSocket.OPEN) return; + + client.subscriptions.forEach((subscription, subId) => { + const matchingTxs = this.getMatchingTransactions(blockData, subscription.filters); + if (matchingTxs.length > 0) { + this.sendBlockUpdate(client, subId, blockData.blockHash, matchingTxs, subscription.filters); + } + }); + }); + } + + private getMatchingTransactions(blockData: FilterBlockResponse, filters: SubscriptionFilter[]): FilteredTxData[] { + return blockData.txs.filter(tx => { + return filters.some(filter => this.matchesFilter(tx, filter, blockData.blockHash)); + }); + } + + private matchesFilter(tx: FilteredTxData, filter: SubscriptionFilter, blockHash: string): boolean { + try { + if (filter.type === 'WILDCARD' && filter.value[0] === '*') { + return true; + } + + const blockWithTx: FilterBlockResponse = { + blockHash, + txs: [tx] + }; + + switch (filter.type) { + case 'CATEGORY': + return this.matchesCategory(blockWithTx, filter); + case 'FROM': + return this.matchesSenderAddress(blockWithTx, filter); + case 'RECIPIENTS': + return this.matchesReceiversAddress(blockWithTx, filter); + default: + return false; + } + } catch (error) { + this.log.error(`Failed to process ${filter.type} filter:`, error); + return false; + } + } + + private sendBlockUpdate( + client: WSClientConnection, + subscriptionId: string, + blockHash: string, + matchingTxs: FilteredTxData[], + filters: SubscriptionFilter[] + ) { + // Get only the filters that matched + const matchedFilters = filters.filter(filter => + matchingTxs.some(tx => this.matchesFilter(tx, filter, blockHash)) + ); + + const updateMessage: BlockUpdateMessage = { + type: 'BLOCK', + data: { + block: { + blockHash, + txs: matchingTxs + }, + subscriptionId, + matchedFilter: matchedFilters // Send only matched filters + }, + timestamp: Date.now() + }; + + client.ws.send(JSON.stringify(updateMessage)); + } + + private matchesCategory(blockData: FilterBlockResponse, filter: SubscriptionFilter): boolean { + return blockData.txs.some(tx => + tx.category && filter.value.includes(tx.category) + ); + } + + private matchesSenderAddress(blockData: FilterBlockResponse, filter: SubscriptionFilter): boolean { + const addresses = filter.value.map(addr => addr.toLowerCase()); + return blockData.txs.some(tx => + tx.from && addresses.includes(tx.from.toLowerCase()) + ); + } + + private matchesReceiversAddress(blockData: FilterBlockResponse, filter: SubscriptionFilter): boolean { + const addresses = filter.value.map(addr => addr.toLowerCase()); + return blockData.txs.some(tx => + tx.recipients?.some(recipient => + addresses.includes(recipient.toLowerCase()) + ) + ); + } + private areValidFilters(filters: SubscriptionFilter[]): boolean { if (!Array.isArray(filters) || filters.length === 0) return false; return filters.every(filter => { switch (filter.type) { - case 'NEW_BLOCK': - return true; case 'CATEGORY': - return typeof filter.value === 'string' && filter.value.length > 0; - case 'SELF': - return true; - case 'SENDERS': - case 'RECEIVERS': - return typeof filter.value === 'string' || Array.isArray(filter.value); + case 'RECIPIENTS': + case 'FROM': + return Array.isArray(filter.value) && filter.value.length > 0; default: return false; } }); } - private generateSubscriptionId(): string { - return `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - } - private handleClientDisconnection(clientId: string, code: number, reason: string) { const client = this.clients.get(clientId); if (!client) return; this.log.info(`Client disconnected: ${clientId}, code: ${code}, reason: ${reason}`); - // Keep client info for potential reconnection - if (code === 1001 || code === 1006) { // Normal closure or abnormal closure + if (code === 1001 || code === 1006) { setTimeout(() => { if (this.clients.has(clientId)) { this.clients.delete(clientId); } - }, 5 * 60 * 1000); // Keep client info for 5 minutes + }, CONSTANTS.RECONNECT_WINDOW); } else { this.clients.delete(clientId); } @@ -314,123 +421,76 @@ export class WebSocketServer { } } - public broadcastBlockUpdate(blockData: BlockData) { - this.clients.forEach((client, clientId) => { - if (client.ws.readyState !== WebSocket.OPEN) return; - // Check each subscription - client.subscriptions.forEach((subscription, subId) => { - // Send notification for each filter, including match status - subscription.filters.forEach(filter => { - try { - // Check if filter matches - let matches = false; - if (filter.type === 'NEW_BLOCK') { - matches = true; - } else if (filter.type === 'CATEGORY' && this.matchesCategory(blockData, filter)) { - matches = true; - } else if (filter.type === 'SELF' && this.matchesSelf(blockData, filter, clientId)) { - matches = true; - } else if (filter.type === 'SENDERS' && this.matchesSenderAddress(blockData, filter)) { - matches = true; - } else if (filter.type === 'RECEIVERS' && this.matchesReceiversAddress(blockData, filter)) { - matches = true; - } - - if (matches) { - const updateMessage: WSMessage = { - type: filter.type, - data: { - block: blockData, - subscriptionId: subId, - matchedFilter: filter, - matches - }, - timestamp: Date.now(), - }; - - client.ws.send(JSON.stringify(updateMessage)); - this.log.debug(`Sent ${filter.type} update to client ${clientId} (subscription: ${subId}, matches: ${matches})`); - } - } catch (error) { - this.log.error(`Failed to process ${filter.type} for client ${clientId}:`, error); - } - }); - }); + private sendWelcomeMessage(clientId: string) { + this.sendToClient(clientId, { + type: 'WELCOME', + data: { clientId }, + timestamp: Date.now() }); } - private matchesCategory(blockData: any, filter: SubscriptionFilter): boolean { - // Check if blockData and required fields exist - if (!blockData?.data_as_json?.txobjList?.[0]?.tx?.category) { - return false; - } - - // Get the category from the first transaction - const txCategory = blockData.data_as_json.txobjList[0].tx.category; - - // Compare with filter value - return txCategory === filter.value; + private sendErrorMessage(clientId: string) { + this.sendToClient(clientId, { + type: 'ERROR', + data: { error: 'Invalid message format' }, + timestamp: Date.now() + }); } - private matchesSelf(blockData: any, filter: SubscriptionFilter, clientId: string): boolean { - const client = this.clients.get(clientId); - if (!client?.clientInfo?.producerAddress) return false; - - const address = client.clientInfo.producerAddress.toLowerCase(); - - // For debugging, only log relevant info - this.log.debug('Matching self for client:', { - clientId, - producerAddress: address + private sendSubscribeError(clientId: string, error: string) { + this.sendToClient(clientId, { + type: 'SUBSCRIBE_ERROR', + data: { error }, + timestamp: Date.now() }); - - // Get sender and recipients from the first transaction - const tx = blockData?.data_as_json?.txobjList?.[0]?.tx; - if (!tx) return false; - - const sender = tx.sender?.toLowerCase(); - const recipients = tx.recipientsList?.map((r: string) => r.toLowerCase()) || []; - - // Check if address matches sender or is in recipients list - return sender === address || recipients.includes(address); } - private matchesSenderAddress(blockData: any, filter: SubscriptionFilter): boolean { - if (!filter.value) return false; - const addresses = (Array.isArray(filter.value) ? filter.value : [filter.value]) - .map(addr => addr.toLowerCase()); - - // Get sender from the first transaction - const sender = blockData?.data_as_json?.txobjList?.[0]?.tx?.sender?.toLowerCase(); - if (!sender) return false; - - // Check if sender matches any of the filter addresses - return addresses.includes(sender); + private sendHandshakeError(clientId: string, error: string) { + this.sendToClient(clientId, { + type: 'HANDSHAKE_ACK', + data: { success: false, error }, + timestamp: Date.now() + }); } - private matchesReceiversAddress(blockData: any, filter: SubscriptionFilter): boolean { - if (!filter.value) return false; - const addresses = (Array.isArray(filter.value) ? filter.value : [filter.value]) - .map(addr => addr.toLowerCase()); - - // Get recipients list from the first transaction - const recipients = blockData?.data_as_json?.txobjList?.[0]?.tx?.recipientsList?.map( - (r: string) => r.toLowerCase() - ) || []; + private sendSubscribeAck(clientId: string, subscriptionId: string, filters: SubscriptionFilter[]) { + this.sendToClient(clientId, { + type: 'SUBSCRIBE_ACK', + data: { + success: true, + subscriptionId, + filters + }, + timestamp: Date.now() + }); + } - // Check if any recipient matches any of the filter addresses - return addresses.some(address => recipients.includes(address)); + private sendPongMessage(clientId: string) { + this.sendToClient(clientId, { + type: 'PONG', + data: { success: true }, + timestamp: Date.now() + }); } - private matchWildcard(str: string, pattern: string): boolean { - const regexPattern = pattern.replace(/\*/g, '.*'); - return new RegExp(`^${regexPattern}$`).test(str); + private generateSubscriptionId(): string { + return `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private generateClientId(): string { return `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; } + private areFiltersEqual(filters1: SubscriptionFilter[], filters2: SubscriptionFilter[]): boolean { + if (filters1.length !== filters2.length) return false; + + return filters1.every((filter1, index) => { + const filter2 = filters2[index]; + return filter1.type === filter2.type && + JSON.stringify(filter1.value) === JSON.stringify(filter2.value); + }); + } + async shutdown() { for (const [clientId, client] of this.clients) { this.log.info(`Closing connection to client ${clientId}`); @@ -441,14 +501,4 @@ export class WebSocketServer { await new Promise((resolve) => this.wss.close(() => resolve())); } } - - private areFiltersEqual(filters1: SubscriptionFilter[], filters2: SubscriptionFilter[]): boolean { - if (filters1.length !== filters2.length) return false; - - return filters1.every((filter1, index) => { - const filter2 = filters2[index]; - return filter1.type === filter2.type && - JSON.stringify(filter1.value) === JSON.stringify(filter2.value); - }); - } } \ No newline at end of file diff --git a/src/services/WebSockets/types.ts b/src/services/WebSockets/types.ts index 9ed6fbe..d6a77b9 100644 --- a/src/services/WebSockets/types.ts +++ b/src/services/WebSockets/types.ts @@ -1,95 +1,171 @@ import WebSocket from 'ws'; -export interface BlockData { - hash: string; - number: number; - timestamp: number; - nodeId: string; - nodeType: string; +// Message Types +export type MessageType = + | 'WELCOME' + | 'HANDSHAKE' + | 'HANDSHAKE_ACK' + | 'SUBSCRIBE' + | 'SUBSCRIBE_ACK' + | 'SUBSCRIBE_ERROR' + | 'UNSUBSCRIBE' + | 'UNSUBSCRIBE_ACK' + | 'BLOCK' + | 'PING' + | 'PONG' + | 'ERROR'; + +// Filter Types +export type FilterType = 'CATEGORY' | 'RECIPIENTS' | 'FROM'; + +// Core Data Interfaces +export interface FilteredTxData { + blockHash: string; + txHash: string; + category: string; + from: string; + recipients: string[]; } -export interface BlockConfirmation { - timestamp: number; - nodes: Set; +export interface FilterBlockResponse { + blockHash: string; + txs: FilteredTxData[]; } -export interface DiscoveryConfig { - refreshInterval: number; - healthCheckTimeout: number; - minArchiveNodes: number; - maxRetries: number; -} - -export interface AuthResponse { - type: 'AUTH_RESPONSE'; - nonce: string; - signature: string; - validatorAddress: string; -} +export type SubscriptionFilter = { + type: 'CATEGORY' | 'FROM' | 'RECIPIENTS' | 'WILDCARD'; + value: string[]; +}; -export interface WSMessage { - type: string; - data?: any; - timestamp: number; +export interface Subscription { + id: string; + filters: SubscriptionFilter[]; } +// Client Related Interfaces export interface ClientInfo { clientId: string; - producerAddress: string; } export interface WSClientConnection { ws: WebSocket; - subscriptions: Map; // subscriptionId -> Subscription + subscriptions: Map; connectedAt: number; clientInfo?: ClientInfo; - reconnectAttempts?: number; + reconnectAttempts: number; lastSubscribeTime?: number; } -export type FilterType = 'NEW_BLOCK' | 'CATEGORY' | 'SELF' | 'SENDERS' | 'RECEIVERS'; +// Message Interfaces +export interface BaseMessage { + type: MessageType; + timestamp: number; +} -export interface BaseFilter { - type: FilterType; +export interface WelcomeMessage extends BaseMessage { + type: 'WELCOME'; + data: { + clientId: string; + }; } -export interface BlockStoredFilter extends BaseFilter { - type: 'NEW_BLOCK'; +export interface HandshakeMessage extends BaseMessage { + type: 'HANDSHAKE'; + data: ClientInfo; } -export interface CategoryFilter extends BaseFilter { - type: 'CATEGORY'; - value: string; // Required string for category name +export interface SubscribeMessage extends BaseMessage { + type: 'SUBSCRIBE'; + filters: SubscriptionFilter[]; } -export interface SelfFilter extends BaseFilter { - type: 'SELF'; +export interface BlockUpdateMessage extends BaseMessage { + type: 'BLOCK'; + data: { + block: FilterBlockResponse; + subscriptionId: string; + matchedFilter: SubscriptionFilter[]; + }; } -export interface SendersFilter extends BaseFilter { - type: 'SENDERS'; - value: string[]; // Required array of sender addresses +export interface BlockReceivedMessage extends BaseMessage { + type: 'BLOCK'; + data: FilterBlockResponse; } -export interface ReceiversFilter extends BaseFilter { - type: 'RECEIVERS'; - value: string[]; // Required array of receiver addresses +export interface ErrorMessage extends BaseMessage { + type: 'ERROR' | 'SUBSCRIBE_ERROR'; + data: { + error: string; + subscriptionId?: string; + matchedFilter?: SubscriptionFilter[]; + }; } -export type SubscriptionFilter = { - type: FilterType; - value?: string | string[]; -}; +export interface AckMessage extends BaseMessage { + type: 'HANDSHAKE_ACK' | 'SUBSCRIBE_ACK' | 'UNSUBSCRIBE_ACK'; + data: { + success: boolean; + error?: string; + subscriptionId?: string; + filters?: SubscriptionFilter[]; + }; +} -export interface Subscription { - id: string; - filters: SubscriptionFilter[]; +export interface UnsubscribeMessage extends BaseMessage { + type: 'UNSUBSCRIBE'; + data: { + subscriptionId: string; + }; } -export interface SubscribeMessage { - filters: SubscriptionFilter[]; +export interface PingMessage extends BaseMessage { + type: 'PING'; +} + +export interface PongMessage extends BaseMessage { + type: 'PONG'; + data: { + success: boolean; + }; +} + +export type WSMessage = + | WelcomeMessage + | HandshakeMessage + | SubscribeMessage + | BlockUpdateMessage + | BlockReceivedMessage + | ErrorMessage + | AckMessage + | UnsubscribeMessage + | PingMessage + | PongMessage; + +// Configuration Interfaces +export interface DiscoveryConfig { + refreshInterval: number; + healthCheckTimeout: number; + minArchiveNodes: number; + maxRetries: number; +} + +export interface BlockData { + hash: string; + number: number; + timestamp: number; + nodeId: string; + nodeType: string; +} + +export interface BlockConfirmation { + timestamp: number; + nodes: Set; } -export interface Transaction { - producerAddress: string; +export interface AuthResponse { + type: 'AUTH_RESPONSE'; + nonce: string; + signature: string; + validatorAddress: string; } \ No newline at end of file From 58617ffe3cceedd9d4137a044c706f6ad954a9ad Mon Sep 17 00:00:00 2001 From: rozaso Date: Thu, 30 Jan 2025 07:57:17 -0800 Subject: [PATCH 05/10] Add high level documentation for websockets. --- .gitignore | 1 + docker/a.yml | 42 ++-- docker/cleanup.sh | 12 +- docker/evm.yml | 2 +- docker/setup.sh | 70 +++--- docker/v1/.env | 5 +- docker/v10/.env | 7 +- docker/v2/.env | 7 +- docker/v3/.env | 7 +- docker/v4/.env | 7 +- docker/v5/.env | 7 +- docker/v6/.env | 7 +- docker/v7/.env | 7 +- docker/v8/.env | 7 +- docker/v9/.env | 7 +- src/app.ts | 2 +- src/services/WebSockets/BlockStatusManager.ts | 36 ++- src/services/WebSockets/DiscoverService.ts | 205 +++++++++++------- src/services/WebSockets/WebSocketClient.ts | 108 +++++++-- src/services/WebSockets/WebSocketManager.ts | 131 +++++++++-- src/services/WebSockets/WebSocketServer.ts | 125 ++++++++++- src/services/WebSockets/types.ts | 2 +- src/utilz/arrayUtil.ts | 9 + 23 files changed, 572 insertions(+), 241 deletions(-) diff --git a/.gitignore b/.gitignore index 451d0c6..255fef0 100644 --- a/.gitignore +++ b/.gitignore @@ -143,4 +143,5 @@ package-lock.json # devtools secrets package-lock.json /docker/external +/docker/debug-*.sh /log diff --git a/docker/a.yml b/docker/a.yml index ddefa12..216f796 100644 --- a/docker/a.yml +++ b/docker/a.yml @@ -24,27 +24,27 @@ services: - ./a1/log:/log - ./_abi/:/config/abi/ - # anode2: - # image: anode-main - # container_name: anode2 - # networks: - # push-dev-network: - # aliases: - # - anode2.local - # environment: - # DB_NAME: anode2 - # PORT: 5002 - # env_file: - # - .env - # - common.env - # - a-specific.env - # ports: - # - "5002:5002" - # entrypoint: ['sh', '/entrypoint.sh'] - # volumes: - # - ./a2:/config - # - ./a2/log:/log - # - ./_abi/:/config/abi/ + anode2: + image: anode-main + container_name: anode2 + networks: + push-dev-network: + aliases: + - anode2.local + environment: + DB_NAME: anode2 + PORT: 5002 + env_file: + - .env + - common.env + - a-specific.env + ports: + - "5002:5002" + entrypoint: ['sh', '/entrypoint.sh'] + volumes: + - ./a2:/config + - ./a2/log:/log + - ./_abi/:/config/abi/ # anode3: diff --git a/docker/cleanup.sh b/docker/cleanup.sh index 27ed0ed..b75be29 100755 --- a/docker/cleanup.sh +++ b/docker/cleanup.sh @@ -11,15 +11,15 @@ esac echo "starting cleanup" -echo "removing s nodes" -docker compose -f s.yml down +# echo "removing s nodes" +# docker compose -f s.yml down -echo "removing v nodes" -docker compose -f v.yml down +# echo "removing v nodes" +# docker compose -f v.yml down -echo "removing a nodes" -docker compose -f a.yml down +# echo "removing a nodes" +# docker compose -f a.yml down echo "removing evm" diff --git a/docker/evm.yml b/docker/evm.yml index 47d346c..ca3a223 100644 --- a/docker/evm.yml +++ b/docker/evm.yml @@ -20,7 +20,7 @@ services: # re-create everything: ./setup.sh VNODE_COUNT: 3 # max 10 SNODE_COUNT: 1 # max 8 - ANODE_COUNT: 1 # max 5 + ANODE_COUNT: 3 # max 5 networks: diff --git a/docker/setup.sh b/docker/setup.sh index caccda7..21b5bfa 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -40,43 +40,43 @@ fi echo -echo "---- STARTING VNODE" -read -p "-> Press ENTER to continue, or 'n' to skip: " response -if [[ "$response" == "n" || "$response" == "N" ]]; then - echo "Skipped" -else - echo - cd "${CHAIN_DIR}/push-vnode" || exit 1 - docker build . -t vnode-main - cd "${DOCKER_DIR}" || exit 1 - docker compose -f v.yml down && docker compose -f v.yml up -d && docker compose -f v.yml logs -f -fi +# echo "---- STARTING VNODE" +# read -p "-> Press ENTER to continue, or 'n' to skip: " response +# if [[ "$response" == "n" || "$response" == "N" ]]; then +# echo "Skipped" +# else +# echo +# cd "${CHAIN_DIR}/push-vnode" || exit 1 +# docker build . -t vnode-main +# cd "${DOCKER_DIR}" || exit 1 +# docker compose -f v.yml down && docker compose -f v.yml up -d && docker compose -f v.yml logs -f +# fi -echo -echo "---- STARTING SNODE" -read -p "-> Press ENTER to continue, or 'n' to skip: " response -if [[ "$response" == "n" || "$response" == "N" ]]; then - echo "Skipped" -else - echo - cd "${CHAIN_DIR}/push-snode" || exit 1 - docker build . -t snode-main - cd "${DOCKER_DIR}" || exit 1 - docker compose -f s.yml down && docker compose -f s.yml up -d && docker compose -f s.yml logs -f -fi +# echo +# echo "---- STARTING SNODE" +# read -p "-> Press ENTER to continue, or 'n' to skip: " response +# if [[ "$response" == "n" || "$response" == "N" ]]; then +# echo "Skipped" +# else +# echo +# cd "${CHAIN_DIR}/push-snode" || exit 1 +# docker build . -t snode-main +# cd "${DOCKER_DIR}" || exit 1 +# docker compose -f s.yml down && docker compose -f s.yml up -d && docker compose -f s.yml logs -f +# fi -echo -echo "---- STARTING ANODE" -read -p "-> Press ENTER to continue, or 'n' to skip: " response -if [[ "$response" == "n" || "$response" == "N" ]]; then - echo "Skipped" -else - echo - cd "${CHAIN_DIR}/push-anode" || exit 1 - docker build -t anode-main -f Dockerfile.light . - cd "${DOCKER_DIR}" || exit 1 - docker compose -f a.yml down && docker compose -f a.yml up -d && docker compose -f a.yml logs -f -fi +# echo +# echo "---- STARTING ANODE" +# read -p "-> Press ENTER to continue, or 'n' to skip: " response +# if [[ "$response" == "n" || "$response" == "N" ]]; then +# echo "Skipped" +# else +# echo +# cd "${CHAIN_DIR}/push-anode" || exit 1 +# docker build -t anode-main -f Dockerfile.light . +# cd "${DOCKER_DIR}" || exit 1 +# docker compose -f a.yml down && docker compose -f a.yml up -d && docker compose -f a.yml logs -f +# fi diff --git a/docker/v1/.env b/docker/v1/.env index ac9bced..c3b84bc 100644 --- a/docker/v1/.env +++ b/docker/v1/.env @@ -1,5 +1,4 @@ WS_MAX_PAYLOAD=5242880 // 5mb -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v10/.env b/docker/v10/.env index 82bf047..c3b84bc 100644 --- a/docker/v10/.env +++ b/docker/v10/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v2/.env b/docker/v2/.env index 82bf047..c3b84bc 100644 --- a/docker/v2/.env +++ b/docker/v2/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v3/.env b/docker/v3/.env index 82bf047..c3b84bc 100644 --- a/docker/v3/.env +++ b/docker/v3/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v4/.env b/docker/v4/.env index 82bf047..c3b84bc 100644 --- a/docker/v4/.env +++ b/docker/v4/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v5/.env b/docker/v5/.env index 82bf047..c3b84bc 100644 --- a/docker/v5/.env +++ b/docker/v5/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v6/.env b/docker/v6/.env index 82bf047..c3b84bc 100644 --- a/docker/v6/.env +++ b/docker/v6/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v7/.env b/docker/v7/.env index 82bf047..c3b84bc 100644 --- a/docker/v7/.env +++ b/docker/v7/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v8/.env b/docker/v8/.env index 82bf047..c3b84bc 100644 --- a/docker/v8/.env +++ b/docker/v8/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/docker/v9/.env b/docker/v9/.env index 82bf047..c3b84bc 100644 --- a/docker/v9/.env +++ b/docker/v9/.env @@ -1,5 +1,4 @@ -WS_MAX_PAYLOAD=5242880 -DISCOVERY_MIN_ARCHIVE_NODES=1 -DISCOVERY_MAX_CONNECTION_RETRIES=3 -DISCOVERY_REFRESH_INTERVAL=60000 +WS_MAX_PAYLOAD=5242880 // 5mb +DISCOVERY_MIN_ARCHIVE_NODES=2 +DISCOVERY_REFRESH_INTERVAL=900000 DISCOVERY_HEALTH_CHECK_TIMEOUT=30000 \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 9bfa3f5..3863033 100644 --- a/src/app.ts +++ b/src/app.ts @@ -102,7 +102,7 @@ async function startServer(logLevel: string = null, testMode = false, padder = 0 const wsManager = Container.get(WebSocketManager); await wsManager.postConstruct(validatorNode.nodeId, validatorContractState.wallet, archivalNodes, server); } catch (error) { - log.error('Failed to initialize WebSocket Manager:', error); + log.error('Failed to initialize WebSocket Manager: %o', error); log.warn('Continuing with HTTP server initialization despite WebSocket failure'); } } diff --git a/src/services/WebSockets/BlockStatusManager.ts b/src/services/WebSockets/BlockStatusManager.ts index f20845c..52f537f 100644 --- a/src/services/WebSockets/BlockStatusManager.ts +++ b/src/services/WebSockets/BlockStatusManager.ts @@ -2,18 +2,29 @@ import { Service } from 'typedi'; import { WinstonUtil } from "../../utilz/winstonUtil"; import { FilterBlockResponse, BlockConfirmation } from './types'; import { WebSocketServer } from './WebSocketServer'; +import { DiscoveryService } from './DiscoverService'; +/** + * Manages the confirmation status of blocks across multiple ANodes. + * Tracks block confirmations and broadcasts updates when required confirmations are met. + */ @Service() export class BlockStatusManager { private confirmations = new Map(); - private readonly REQUIRED_CONFIRMATIONS = 1; - private readonly CONFIRMATION_EXPIRY_TIME = 3 * 60 * 60 * 1000; // 3 hours in milliseconds - private readonly log = WinstonUtil.newLog("BlockStatusManager"); + private readonly CONFIRMATION_EXPIRY_TIME = 30 * 60 * 1000; // 30 minutes + private readonly log = WinstonUtil.newLog(BlockStatusManager); constructor( - private readonly wsServer: WebSocketServer + private readonly wsServer: WebSocketServer, + private readonly discoveryService: DiscoveryService ) {} + /** + * Processes a block confirmation from an ANode and triggers broadcast if confirmation threshold is met. + * @param blockHash - The hash of the block being confirmed + * @param nodeId - The ID of the ANode confirming the block + * @param blockData - The block data received from the ANode + */ async handleBlockConfirmation(blockHash: string, nodeId: string, blockData: FilterBlockResponse) { let confirmation = this.confirmations.get(blockHash); if (!confirmation) { @@ -27,17 +38,28 @@ export class BlockStatusManager { confirmation.nodes.add(nodeId); this.log.debug(`Block ${blockHash} confirmed by ANode: ${nodeId}. Total confirmations: ${confirmation.nodes.size}`); - if (confirmation.nodes.size >= this.REQUIRED_CONFIRMATIONS) { + if (confirmation.nodes.size >= this.discoveryService.getMinArchiveNodes()) { await this.handleBlockConfirmed(blockHash, blockData); } } + /** + * Handles the logic when a block reaches the required number of confirmations. + * Broadcasts the block update to connected clients and removes it from tracking. + * @param blockHash - The hash of the confirmed block + * @param blockData - The block data to broadcast + * @private + */ private async handleBlockConfirmed(blockHash: string, blockData: FilterBlockResponse) { this.log.info(`Block ${blockHash} reached required confirmations`); this.wsServer.broadcastBlockUpdate(blockData); this.confirmations.delete(blockHash); } + /** + * Removes block confirmations that have exceeded the expiry time. + * @private + */ private cleanupOldConfirmations() { const now = Date.now(); for (const [blockHash, confirmation] of this.confirmations.entries()) { @@ -48,6 +70,10 @@ export class BlockStatusManager { } } + /** + * Initiates the cleanup of expired block confirmations. + * Should be called periodically to prevent memory leaks. + */ public performCleanup() { this.cleanupOldConfirmations(); } diff --git a/src/services/WebSockets/DiscoverService.ts b/src/services/WebSockets/DiscoverService.ts index cb096c5..b924dc0 100644 --- a/src/services/WebSockets/DiscoverService.ts +++ b/src/services/WebSockets/DiscoverService.ts @@ -4,86 +4,129 @@ import { DiscoveryConfig } from './types'; import { EnvLoader } from "../../utilz/envLoader"; import { NodeInfo } from '../messaging-common/validatorContractState'; import { WinstonUtil } from '../../utilz/winstonUtil'; +import { EventEmitter } from 'events'; +import { ArrayUtil } from '../../utilz/arrayUtil'; -export class DiscoveryService { +/** + * Service responsible for discovering and maintaining connections to Archive Nodes (ANodes). + * Manages health checks and ensures a minimum number of active connections are maintained. + * Emits events when connection state changes. + * + * Events: + * - 'minimumNodesConnected': Emitted when minimum required connections are established + * - 'minimumNodesLost': Emitted when connections fall below minimum threshold + */ +export class DiscoveryService extends EventEmitter { private archiveNodes = new Map(); private refreshInterval: NodeJS.Timer; private vNodeId: string; private archivalNodes: Map; private readonly log = WinstonUtil.newLog("DiscoveryService"); + private previousState: 'connected' | 'disconnected' = 'disconnected'; constructor( private readonly config: DiscoveryConfig ) { + super(); this.config = { - refreshInterval: EnvLoader.getPropertyAsNumber("DISCOVERY_REFRESH_INTERVAL", 60000), - healthCheckTimeout: EnvLoader.getPropertyAsNumber("DISCOVERY_HEALTH_CHECK_TIMEOUT", 5000), - minArchiveNodes: EnvLoader.getPropertyAsNumber("DISCOVERY_MIN_ARCHIVE_NODES", 1), - maxRetries: EnvLoader.getPropertyAsNumber("DISCOVERY_MAX_CONNECTION_RETRIES", 3) + refreshInterval: EnvLoader.getPropertyAsNumber("DISCOVERY_REFRESH_INTERVAL", 900000), + healthCheckTimeout: EnvLoader.getPropertyAsNumber("DISCOVERY_HEALTH_CHECK_TIMEOUT", 30000), + minArchiveNodes: EnvLoader.getPropertyAsNumber("DISCOVERY_MIN_ARCHIVE_NODES", 2) }; } + /** + * Initializes the discovery service and starts periodic health checks. + * Attempts initial connections and sets up refresh interval. + * + * @param vNodeId - Unique identifier for the validator node + * @param archivalNodes - Map of available archive nodes to connect to + */ async initialize(vNodeId: string, archivalNodes: Map) { this.vNodeId = vNodeId; this.archivalNodes = archivalNodes; - // Initial connection with retry mechanism - await this.ensureMinimumConnections(); + // Initial connection - single attempt + const initialSuccess = await this.ensureMinimumConnections(); + + // Set initial state + this.previousState = initialSuccess ? 'connected' : 'disconnected'; + + // Emit initial state + if (initialSuccess) { + this.emit('minimumNodesConnected'); + } else { + this.emit('minimumNodesLost'); + } // Start periodic refresh this.refreshInterval = setInterval( - () => this.refreshNodeStatus(), + async () => { + await this.refreshNodeStatus(); + + // Check current state + const currentState = this.archiveNodes.size >= this.config.minArchiveNodes + ? 'connected' + : 'disconnected'; + + // Only emit events if state has changed + if (currentState !== this.previousState) { + if (currentState === 'connected') { + this.emit('minimumNodesConnected'); + } else { + this.log.error('Lost minimum required ANode connections, triggering cleanup'); + this.emit('minimumNodesLost'); + } + this.previousState = currentState; + } + }, this.config.refreshInterval ); } - private async ensureMinimumConnections(): Promise { - let retryCount = 0; - - while (this.archiveNodes.size < this.config.minArchiveNodes && retryCount < this.config.maxRetries) { - this.log.info(`Attempting to establish minimum ANode connections. Current: ${this.archiveNodes.size}, Target: ${this.config.minArchiveNodes}`); + /** + * Attempts to establish minimum required connections to archive nodes. + * Randomly shuffles available nodes and attempts connections until minimum is reached. + * + * @returns Promise - True if minimum connections established, false otherwise + */ + private async ensureMinimumConnections(): Promise { + this.log.info(`Available ANodes: ${this.archivalNodes.size}, Needed connections to: ${this.config.minArchiveNodes}`); - // Get available nodes that aren't already connected - const availableNodes = Array.from(this.archivalNodes.entries()) - .filter(([nodeId]) => !this.archiveNodes.has(nodeId)); + // Get all available nodes + const availableNodes = Array.from(this.archivalNodes.entries()); - if (availableNodes.length === 0) { - this.log.error('No more available nodes to connect to'); + // Randomize the order of connection attempts + const shuffledNodes = ArrayUtil.shuffleArray(availableNodes); + + // Try each available node until we reach our minimum or run out of nodes + for (const [nodeId, nodeInfo] of shuffledNodes) { + if (this.archiveNodes.size >= this.config.minArchiveNodes) { + this.log.info('Reached minimum required connections'); break; } - - // Shuffle available nodes for random selection - const shuffledNodes = this.shuffleArray(availableNodes); - - // Try to connect to nodes until we reach minimum or run out of nodes - for (const [nodeId, nodeInfo] of shuffledNodes) { - if (this.archiveNodes.size >= this.config.minArchiveNodes) break; - - const wsUrl = nodeInfo.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); - await this.addNode(wsUrl, nodeInfo); - } - - retryCount++; - if (this.archiveNodes.size < this.config.minArchiveNodes) { - await new Promise(resolve => setTimeout(resolve, 1000)); // Wait before retry - } - } - - if (this.archiveNodes.size < this.config.minArchiveNodes) { - this.log.error(`Failed to establish minimum required connections. Current: ${this.archiveNodes.size}, Required: ${this.config.minArchiveNodes}`); + const wsUrl = nodeInfo.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); + await this.addNode(wsUrl, nodeInfo); } - } - private shuffleArray(array: T[]): T[] { - const shuffled = [...array]; - for (let i = shuffled.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + const success = this.archiveNodes.size >= this.config.minArchiveNodes; + if (!success) { + this.log.warn(`Initial connection attempt: Could not establish minimum required connections. ` + + `Connected: ${this.archiveNodes.size}, Required: ${this.config.minArchiveNodes}. ` + + `Periodic refresh will continue trying.`); + } else { + this.log.info(`Successfully established initial ${this.archiveNodes.size} connections`); } - return shuffled; + + return success; } + /** + * Returns list of currently active and healthy archive nodes. + * + * @returns Promise - Array of active node information + */ async getActiveArchiveNodes(): Promise { const nodes = Array.from(this.archiveNodes.values()); return nodes.filter(node => node.nodeStatus === 0); @@ -94,15 +137,22 @@ export class DiscoveryService { const isHealthy = await this.checkNodeHealth(wsUrl); if (isHealthy) { this.archiveNodes.set(nodeInfo.nodeId, nodeInfo); - this.log.info(`Successfully added healthy ANode ${nodeInfo.nodeId}`); + this.log.info(`Successfully added healthy ANode ${nodeInfo.nodeId} with url: ${wsUrl}`); } else { this.log.warn(`ANode ${nodeInfo.nodeId} not added - health check failed`); } } catch (error) { - this.log.error(`Failed to add node ${wsUrl}:`, error); + this.log.error(`Failed to add node ${wsUrl}: %o`, error); } } + /** + * Performs health check on a node via WebSocket connection. + * Tests connection, authentication, and health check response within timeout period. + * + * @param wsUrl - WebSocket URL of the node to check + * @returns Promise - True if node is healthy, false otherwise + */ private async checkNodeHealth(wsUrl: string): Promise { return new Promise((resolve) => { try { @@ -126,7 +176,7 @@ export class DiscoveryService { ws.on('message', (data: Buffer) => { try { const message = JSON.parse(data.toString()); - this.log.info(`Received ${message.type} from ANode at ${wsUrl}:`, JSON.stringify(message, null, 2)); + this.log.info(`Received ${message.type} from ANode at ${wsUrl}: %o`, message); if (message.type === 'AUTH_CHALLENGE' && message.nonce) { const healthCheck = { @@ -142,7 +192,7 @@ export class DiscoveryService { resolve(true); } } catch (error) { - this.log.error(`Failed to process message from ANode at ${wsUrl}:`, error); + this.log.error(`Failed to process message from ANode at ${wsUrl}: %o`, error); cleanup(); ws.close(); resolve(false); @@ -150,7 +200,7 @@ export class DiscoveryService { }); ws.on('error', (error) => { - this.log.error(`WebSocket error for ANode at ${wsUrl}:`, error); + this.log.error(`WebSocket error for ANode at ${wsUrl}: %o`, error); cleanup(); resolve(false); }); @@ -160,13 +210,19 @@ export class DiscoveryService { ws.removeAllListeners(); }; } catch (error) { - this.log.error(`Failed to establish WebSocket connection to ANode at ${wsUrl}:`, error); + this.log.error(`Failed to establish WebSocket connection to ANode at ${wsUrl}: %o`, error); resolve(false); } }); } + /** + * Periodic refresh of node status. Checks health of all connected nodes + * and attempts to establish new connections if below minimum threshold. + * Updates internal state and emits events on state changes. + */ private async refreshNodeStatus() { + this.log.info(`Refreshing node status...`); const updatedNodes = new Map(); // First check existing nodes @@ -183,48 +239,33 @@ export class DiscoveryService { } } - // If we're below minimum, try to add new nodes - while (updatedNodes.size < this.config.minArchiveNodes) { - // Get available nodes that aren't already in use - const availableNodes = Array.from(this.archivalNodes.entries()) - .filter(([nodeId]) => !updatedNodes.has(nodeId)); - - if (availableNodes.length === 0) { - this.log.error('No more available ANodes to try'); - break; - } - - // Pick a random available node - const shuffledNodes = this.shuffleArray(availableNodes); - const [nodeId, nodeInfo] = shuffledNodes[0]; - - const wsUrl = nodeInfo.url.replace(/^http/, 'ws').replace(/^https/, 'wss'); - const isHealthy = await this.checkNodeHealth(wsUrl); - - if (isHealthy) { - updatedNodes.set(nodeId, { - ...nodeInfo, - nodeStatus: 0 - }); - this.log.info(`Added new healthy ANode ${nodeId}`); - } else { - this.log.warn(`Attempted ANode ${nodeId} is not healthy, trying another`); - } - } - - // Update the archive nodes map + // Update the archive nodes map with currently healthy nodes this.archiveNodes = updatedNodes; + // If we're below minimum, try to establish more connections if (this.archiveNodes.size < this.config.minArchiveNodes) { - this.log.error(`Failed to maintain minimum required archive nodes. Current: ${this.archiveNodes.size}, Required: ${this.config.minArchiveNodes}`); + this.log.info(`Healthy nodes (${this.archiveNodes.size}) below minimum (${this.config.minArchiveNodes}), attempting to establish more connections`); + await this.ensureMinimumConnections(); } else { this.log.info(`Successfully maintaining ${this.archiveNodes.size} healthy archive nodes`); } } + /** + * Cleans up resources by clearing refresh interval. + */ async destroy() { if (this.refreshInterval) { clearInterval(this.refreshInterval); } } + + /** + * Returns the minimum number of archive nodes required for operation. + * + * @returns number - Minimum required archive nodes + */ + public getMinArchiveNodes(): number { + return this.config.minArchiveNodes; + } } diff --git a/src/services/WebSockets/WebSocketClient.ts b/src/services/WebSockets/WebSocketClient.ts index 9def3cf..e159db5 100644 --- a/src/services/WebSockets/WebSocketClient.ts +++ b/src/services/WebSockets/WebSocketClient.ts @@ -9,6 +9,11 @@ import { BitUtil } from '../../utilz/bitUtil'; import { EthUtil } from '../../utilz/ethUtil'; import { Wallet } from 'ethers'; +/** + * Manages WebSocket connections to Archive Nodes, handling authentication, + * reconnection logic, and message processing. + * Maintains connection state and ensures minimum required connections are available. + */ @Service() export class WebSocketClient { private readonly MIN_ARCHIVE_CONNECTIONS = 1; @@ -36,6 +41,14 @@ export class WebSocketClient { private readonly blockManager: BlockStatusManager, ) {} + /** + * Initializes the WebSocket client with validator ID and wallet. + * Establishes initial connections and starts monitoring. + * + * @param vNodeId - Validator node identifier + * @param wallet - Ethereum wallet for authentication + * @throws Error if initialization fails + */ async postConstruct(vNodeId: string, wallet: Wallet) { try { this.vNodeId = vNodeId; @@ -44,11 +57,15 @@ export class WebSocketClient { this.startConnectionMonitoring(); this.log.info('WebSocket client initialized'); } catch (error) { - this.log.error('Failed to initialize WebSocket client:', error); + this.log.error('Failed to initialize WebSocket client: %o', error); throw error; } } + /** + * Establishes connections to archive nodes up to the minimum required count. + * @throws Error if insufficient active nodes are available + */ private async initializeConnections() { this.log.info('Initializing archive node connections'); const activeNodes = await this.discoveryService.getActiveArchiveNodes(); @@ -64,6 +81,13 @@ export class WebSocketClient { } } + /** + * Establishes a WebSocket connection to an archive node with authentication. + * Handles the complete connection flow including challenge-response authentication. + * + * @param node - Archive node information + * @returns Promise that resolves when connection is established and authenticated + */ private async connectToArchiveNode(node: NodeInfo) { if (this.archiveConnections.has(node.nodeId)) { this.log.warn(`Already connected to node ${node.nodeId}`); @@ -107,7 +131,7 @@ export class WebSocketClient { signature, validatorAddress: this.vNodeId }; - this.log.info(`Sending auth response to ${node.nodeId}:`, response); + this.log.info(`Sending auth response to ${node.nodeId}: %o`, response); ws.send(JSON.stringify(response)); } else if (message.type === 'AUTH_SUCCESS') { this.log.info(`Authentication successful for ${node.nodeId}`); @@ -134,7 +158,7 @@ export class WebSocketClient { const onError = (error: Error) => { cleanup(); ws.terminate(); - this.log.error(`[${this.vNodeId}] Connection error to ${node.nodeId}:`, error); + this.log.error(`[${this.vNodeId}] Connection error to ${node.nodeId}: %o`, error); reject(error); }; @@ -150,6 +174,12 @@ export class WebSocketClient { }); } + /** + * Subscribes to block events from an archive node. + * + * @param ws - WebSocket connection + * @param nodeId - Archive node identifier + */ private subscribeToEvents(ws: WebSocket, nodeId: string) { const subscribeMessage = { type: 'SUBSCRIBE', @@ -160,19 +190,26 @@ export class WebSocketClient { try { ws.send(JSON.stringify(subscribeMessage)); - this.log.debug(`vNode ${this.vNodeId} subscribed to events on anode ${nodeId}:`, subscribeMessage); + this.log.debug(`vNode ${this.vNodeId} subscribed to events on anode ${nodeId}: %o`, subscribeMessage); } catch (error) { - this.log.error(`vNode ${this.vNodeId} failed to subscribe to events on anode ${nodeId}:`, error); + this.log.error(`vNode ${this.vNodeId} failed to subscribe to events on anode ${nodeId}: %o`, error); ws.close(); } } + /** + * Processes incoming messages from archive nodes. + * Currently handles BLOCK messages and forwards them to BlockStatusManager. + * + * @param nodeId - Source archive node identifier + * @param message - Received WebSocket message + */ private handleArchiveMessage(nodeId: string, message: WSMessage) { if (message.type === 'BLOCK') { const blockData = (message as BlockReceivedMessage).data; if (!blockData?.blockHash ) { - this.log.warn(`Received invalid BLOCK message structure from ${nodeId}:`, message); + this.log.warn(`Received invalid BLOCK message structure from ${nodeId}: %o`, message); return; } @@ -183,17 +220,24 @@ export class WebSocketClient { blockData ); } else { - this.log.warn(`Received unexpected message type from ${nodeId}:`, message); + this.log.warn(`Received unexpected message type from ${nodeId}: %o`, message); } } + /** + * Sets up WebSocket event handlers for a connection. + * Handles messages, connection closure, errors, and ping/pong heartbeat. + * + * @param nodeId - Archive node identifier + * @param ws - WebSocket connection + */ private setupWebSocketHandlers(nodeId: string, ws: WebSocket) { const messageHandler = (data: string) => { try { const message = JSON.parse(data) as WSMessage; this.handleArchiveMessage(nodeId, message); } catch (error) { - this.log.error(`Message parsing error from ${nodeId}:`, error); + this.log.error(`Message parsing error from ${nodeId}: %o`, error); } }; @@ -216,7 +260,7 @@ export class WebSocketClient { }; const errorHandler = (error: Error) => { - this.log.error(`WebSocket error for ${nodeId}:`, error); + this.log.error(`WebSocket error for ${nodeId}: %o`, error); ws.close(); }; @@ -239,7 +283,7 @@ export class WebSocketClient { this.updateConnectionState(nodeId, { lastPing: Date.now() }); ws.ping((err) => { if (err) { - this.log.warn(`Ping failed for ${nodeId}:`, err); + this.log.warn(`Ping failed for ${nodeId}: %o`, err); ws.close(); } }); @@ -258,6 +302,13 @@ export class WebSocketClient { ws.on('error', errorHandler); } + /** + * Handles disconnection from an archive node. + * Implements exponential backoff reconnection strategy. + * Falls back to alternative nodes if reconnection fails. + * + * @param nodeId - Disconnected node identifier + */ private async handleDisconnect(nodeId: string) { const attempts = this.reconnectAttempts.get(nodeId) || 0; @@ -287,7 +338,7 @@ export class WebSocketClient { throw new Error(`Node ${nodeId} no longer available`); } } catch (error) { - this.log.error(`Failed to reconnect to ${nodeId}:`, error); + this.log.error(`Failed to reconnect to ${nodeId}: %o`, error); this.reconnectAttempts.set(nodeId, attempts + 1); if ((attempts + 1) >= this.maxReconnectAttempts) { @@ -296,6 +347,10 @@ export class WebSocketClient { } } + /** + * Attempts to find and connect to an alternative archive node. + * Used when primary connections fail or are insufficient. + */ private async findAlternativeNode() { try { const activeNodes = await this.discoveryService.getActiveArchiveNodes(); @@ -308,16 +363,24 @@ export class WebSocketClient { await this.connectToArchiveNode(unusedNodes[0]); } } catch (error) { - this.log.error('Failed to find alternative node:', error); + this.log.error('Failed to find alternative node: %o', error); } } + /** + * Starts periodic connection monitoring. + * Checks connection health and restores connections if needed. + */ private startConnectionMonitoring() { this.reconnectTimer = setInterval(() => { this.checkAndRestoreConnections(); }, this.MONITOR_INTERVAL); // Check connections every minute } + /** + * Gracefully shuts down all WebSocket connections. + * Cleans up resources and connection states. + */ async shutdown() { try { if (this.reconnectTimer) { @@ -337,7 +400,7 @@ export class WebSocketClient { this.archiveConnections.clear(); this.reconnectAttempts.clear(); } catch (error) { - this.log.error('Error during shutdown:', error); + this.log.error('Error during shutdown: %o', error); } } @@ -354,7 +417,7 @@ export class WebSocketClient { await this.initializeConnections(); } } catch (error) { - this.log.error('Failed to restore connections:', error); + this.log.error('Failed to restore connections: %o', error); } } @@ -362,6 +425,10 @@ export class WebSocketClient { return ws.readyState === WebSocket.OPEN; } + /** + * Validates existing connections and removes stale ones. + * Checks both WebSocket state and ping/pong timeouts. + */ private async validateConnections() { const now = Date.now(); for (const [nodeId, ws] of this.archiveConnections) { @@ -383,11 +450,24 @@ export class WebSocketClient { } } + /** + * Calculates reconnection delay using exponential backoff. + * + * @param attempts - Number of previous reconnection attempts + * @returns Delay in milliseconds before next reconnection attempt + */ private getReconnectDelay(attempts: number): number { const delay = this.baseReconnectDelay * Math.pow(2, attempts); return Math.min(delay, this.MAX_RECONNECT_DELAY); } + /** + * Updates the connection state for a node. + * Tracks ping/pong timestamps and reconnection status. + * + * @param nodeId - Archive node identifier + * @param update - Partial state update + */ private updateConnectionState(nodeId: string, update: Partial<{ lastPing: number; lastPong: number; diff --git a/src/services/WebSockets/WebSocketManager.ts b/src/services/WebSockets/WebSocketManager.ts index e8a9370..81c2634 100644 --- a/src/services/WebSockets/WebSocketManager.ts +++ b/src/services/WebSockets/WebSocketManager.ts @@ -9,11 +9,28 @@ import { NodeInfo } from '../messaging-common/validatorContractState'; import { Wallet } from 'ethers' import { Server } from 'http'; +/** + * Manages WebSocket server and client components for the Push vNode. + * Coordinates initialization, shutdown, and lifecycle management based on archival node availability. + * Handles the orchestration between WebSocket server, client, and discovery service components. + */ @Service() export class WebSocketManager { private readonly log = WinstonUtil.newLog(WebSocketManager); private cleanupInterval?: NodeJS.Timeout; + private wsServerInitialized = false; + private wsClientInitialized = false; + private pendingServer: Server | null = null; + private pendingVNodeId: string | null = null; + private pendingWallet: Wallet | null = null; + /** + * Initializes the WebSocket manager with required dependencies. + * @param wsClient - WebSocket client for connecting to other nodes + * @param wsServer - WebSocket server for handling incoming connections + * @param blockManager - Manages block status and cleanup + * @param discoveryService - Handles archival node discovery and connection management + */ constructor( private readonly wsClient: WebSocketClient, private readonly wsServer: WebSocketServer, @@ -21,23 +38,9 @@ export class WebSocketManager { private readonly discoveryService: DiscoveryService ) {} - async postConstruct(vNodeId: string, wallet: Wallet, archivalNodes: Map, server: Server) { - try { - if (!archivalNodes) { - throw new Error('archivalNodes is undefined'); - } - - await this.discoveryService.initialize(vNodeId, archivalNodes); - await this.wsServer.postConstruct(server); - await this.wsClient.postConstruct(vNodeId, wallet); - - // Store interval reference - this.cleanupInterval = setInterval(() => { - this.blockManager.performCleanup(); - }, 60 * 60 * 1000); // Every hour - - let artwork = - ` + private displayInitializationArtwork() { + let artwork = +` ____ _ __ __ _ _ _ _ | _ \\ _ _ ___| |__ \\ \\ / /_ _| (_) __| | __ _| |_ ___ _ __ | |_) | | | / __| '_ \\ \\ V / _\` | | |/ _\` |/ _\` | __/ _ \\| '__| @@ -50,19 +53,99 @@ __ __ _ ____ _ _ \\_/\\_/ \\___|_.__/____/ \\___|\\___|_|\\_\\___|\\__| `; - console.log(` - ################################################ - ${artwork} + console.log(` + ################################################ + ${artwork} + + 🛡️ WebSocket Server and Client Initialized Successfully 🛡️ + ################################################ + `); + } + + /** + * Initializes the WebSocket components after class construction. + * Sets up event handlers for node discovery and manages component lifecycle. + * @param vNodeId - Unique identifier for this validator node + * @param wallet - Ethereum wallet for authentication + * @param archivalNodes - Map of available archival nodes + * @param server - HTTP server instance to attach WebSocket server to + * @throws Error if archivalNodes is undefined or initialization fails + */ + async postConstruct(vNodeId: string, wallet: Wallet, archivalNodes: Map, server: Server) { + try { + if (!archivalNodes) { + throw new Error('archivalNodes is undefined'); + } + + // Store these for later use if needed + this.pendingServer = server; + this.pendingVNodeId = vNodeId; + this.pendingWallet = wallet; + + // Set up discovery service event handlers + this.discoveryService.on('minimumNodesConnected', async () => { + if (!this.wsServerInitialized || !this.wsClientInitialized) { + try { + if (!this.wsServerInitialized) { + await this.wsServer.postConstruct(this.pendingServer!); + this.wsServerInitialized = true; + this.log.info('WebSocket server initialized after meeting minimum node requirement'); + } + + if (!this.wsClientInitialized) { + await this.wsClient.postConstruct(this.pendingVNodeId!, this.pendingWallet!); + this.wsClientInitialized = true; + this.log.info('WebSocket client initialized after meeting minimum node requirement'); + } + + if (this.wsServerInitialized && this.wsClientInitialized) { + this.displayInitializationArtwork(); + } + } catch (error) { + this.log.error('Failed to initialize WebSocket components: %o', error); + } + } + }); + + this.discoveryService.on('minimumNodesLost', async () => { + this.log.warn('Minimum required archive nodes not available, shutting down WebSocket components'); + + try { + // Cleanup WebSocket components + if (this.wsServerInitialized) { + await this.wsServer.shutdown(); + this.wsServerInitialized = false; + this.log.info('WebSocket server shut down due to insufficient ANodes'); + } + + if (this.wsClientInitialized) { + await this.wsClient.shutdown(); + this.wsClientInitialized = false; + this.log.info('WebSocket client shut down due to insufficient ANodes'); + } + } catch (error) { + this.log.error('Error during WebSocket components shutdown: %o', error); + } + }); + + // Initialize discovery service + await this.discoveryService.initialize(vNodeId, archivalNodes); + + // Store interval reference + this.cleanupInterval = setInterval(() => { + this.blockManager.performCleanup(); + }, 15 * 60 * 1000); // Every 15 minutes - 🛡️ WebSocket Server and Client Initialized Successfully 🛡️ - ################################################ - `); } catch (error) { - this.log.error('Failed to initialize WebSocket manager:', error); + this.log.error('Failed to initialize WebSocketManager: %o', error); throw error; } } + /** + * Gracefully shuts down all WebSocket components and cleans up resources. + * Clears cleanup interval and terminates both client and server connections. + */ async shutdown() { // Clear the interval if (this.cleanupInterval) { diff --git a/src/services/WebSockets/WebSocketServer.ts b/src/services/WebSockets/WebSocketServer.ts index 49a6aee..fcdc243 100644 --- a/src/services/WebSockets/WebSocketServer.ts +++ b/src/services/WebSockets/WebSocketServer.ts @@ -15,23 +15,38 @@ import { } from './types'; const CONSTANTS = { - HANDSHAKE_TIMEOUT: 30000, - HEARTBEAT_INTERVAL: 30000, - RECONNECT_WINDOW: 5 * 60 * 1000, - SUBSCRIPTION_RATE_LIMIT: 1000, + HANDSHAKE_TIMEOUT: 30000, // 30 seconds + HEARTBEAT_INTERVAL: 30000, // 30 seconds + RECONNECT_WINDOW: 60 * 1000, // 1 minute + SUBSCRIPTION_RATE_LIMIT: 1000, // 1 second } as const; +/** + * WebSocket server implementation for handling real-time client connections and subscriptions. + * Manages client connections, authentication, subscriptions, and real-time block updates. + * + * Features: + * - Client handshake and authentication + * - Subscription management with filtering + * - Real-time block updates + * - Connection health monitoring + * - Rate limiting for subscriptions + */ @Service() export class WebSocketServer { private wss: WebSocket.Server; private clients = new Map(); private readonly log = WinstonUtil.newLog("WebSocketServer"); + /** + * Initializes the WebSocket server after class construction. + * @param server - HTTP server instance to attach the WebSocket server to + */ async postConstruct(server: Server) { try { await this.initialize(server); } catch (error) { - this.log.error('Failed to initialize WebSocket server:', error); + this.log.error('Failed to initialize WebSocket server: %o', error); throw error; } } @@ -39,9 +54,8 @@ export class WebSocketServer { private async initialize(server: Server) { this.wss = new WebSocket.Server({ server, - maxPayload: EnvLoader.getPropertyAsNumber('WS_MAX_PAYLOAD', 5 * 1024 * 1024), + maxPayload: EnvLoader.getPropertyAsNumber('WS_MAX_PAYLOAD', 5 * 1024 * 1024), // 5MB verifyClient: (info, cb) => { - // During testing, accept all connections cb(true); } }); @@ -84,6 +98,14 @@ export class WebSocketServer { this.sendWelcomeMessage(clientId); } + /** + * Initializes a new client connection with basic state. + * Sets up connection tracking and logging. + * + * @param clientId - Unique identifier for the client + * @param ws - WebSocket connection instance + * @returns Initialized client connection object + */ private initializeClient(clientId: string, ws: WebSocket): WSClientConnection { const client: WSClientConnection = { ws, @@ -96,6 +118,15 @@ export class WebSocketServer { return client; } + /** + * Sets up client timeouts for handshake and heartbeat. + * - Handshake must complete within HANDSHAKE_TIMEOUT + * - Regular ping messages sent at HEARTBEAT_INTERVAL + * + * @param clientId - Client identifier + * @param client - Client connection object + * @returns Cleanup handlers for timeouts + */ private setupClientTimeouts(clientId: string, client: WSClientConnection) { const handshakeTimeout = setTimeout(() => { if (!client.clientInfo) { @@ -120,7 +151,7 @@ export class WebSocketServer { this.log.debug(`Received ${message.type} from ${clientId}`); this.handleClientMessage(clientId, message); } catch (error) { - this.log.error(`Failed to parse message from client ${clientId}:`, error); + this.log.error(`Failed to parse message from client ${clientId}: %o`, error); this.sendErrorMessage(clientId); } }); @@ -132,11 +163,18 @@ export class WebSocketServer { }); ws.on('error', (error) => { - this.log.error(`Client ${clientId} error:`, error); + this.log.error(`Client ${clientId} error: %o`, error); this.handleClientDisconnection(clientId, 1006, 'Connection error'); }); } + /** + * Processes incoming client messages based on type. + * Handles: HANDSHAKE, SUBSCRIBE, UNSUBSCRIBE, PING + * + * @param clientId - Client identifier + * @param message - Parsed WebSocket message + */ private handleClientMessage(clientId: string, message: WSMessage) { const client = this.clients.get(clientId); if (!client) { @@ -166,6 +204,14 @@ export class WebSocketServer { } } + /** + * Processes client handshake requests. + * Validates handshake data and clientId match. + * + * @param clientId - Client identifier + * @param client - Client connection object + * @param message - Handshake message + */ private handleHandshake(clientId: string, client: WSClientConnection, message: WSMessage) { if (message.type !== 'HANDSHAKE' || !this.isValidHandshakeData(message.data)) { this.sendHandshakeError(clientId, 'Invalid handshake data'); @@ -189,6 +235,16 @@ export class WebSocketServer { }); } + /** + * Processes subscription requests from clients. + * Validates filters and manages subscription creation. + * Handles special case for wildcard subscriptions. + * Implements rate limiting for subscription requests. + * + * @param clientId - Client identifier + * @param client - Client connection object + * @param message - Subscription request message + */ private handleSubscribe(clientId: string, client: WSClientConnection, message: WSMessage) { if (message.type !== 'SUBSCRIBE') { this.sendSubscribeError(clientId, 'Invalid subscription message'); @@ -281,6 +337,10 @@ export class WebSocketServer { return subscriptionId; } + /** + * Broadcasts block updates to all connected clients based on their subscription filters. + * @param blockData - Block data containing transactions to be filtered and broadcast + */ public broadcastBlockUpdate(blockData: FilterBlockResponse) { this.clients.forEach((client, clientId) => { if (client.ws.readyState !== WebSocket.OPEN) return; @@ -300,6 +360,15 @@ export class WebSocketServer { }); } + /** + * Filters transactions based on subscription criteria. + * Supports multiple filter types: CATEGORY, FROM, RECIPIENTS, WILDCARD + * + * @param tx - Transaction data to check + * @param filter - Subscription filter to apply + * @param blockHash - Hash of the block containing the transaction + * @returns boolean indicating if transaction matches filter + */ private matchesFilter(tx: FilteredTxData, filter: SubscriptionFilter, blockHash: string): boolean { try { if (filter.type === 'WILDCARD' && filter.value[0] === '*') { @@ -322,11 +391,21 @@ export class WebSocketServer { return false; } } catch (error) { - this.log.error(`Failed to process ${filter.type} filter:`, error); + this.log.error(`Failed to process ${filter.type} filter: %o`, error); return false; } } + /** + * Sends block update to client for matching transactions. + * Only includes filters that matched the transactions. + * + * @param client - Client connection object + * @param subscriptionId - Subscription identifier + * @param blockHash - Hash of the block + * @param matchingTxs - Transactions matching subscription filters + * @param filters - Original subscription filters + */ private sendBlockUpdate( client: WSClientConnection, subscriptionId: string, @@ -392,6 +471,14 @@ export class WebSocketServer { }); } + /** + * Handles client disconnection events. + * Implements reconnection window for specific disconnect codes. + * + * @param clientId - Client identifier + * @param code - Disconnection status code + * @param reason - Disconnection reason + */ private handleClientDisconnection(clientId: string, code: number, reason: string) { const client = this.clients.get(clientId); if (!client) return; @@ -409,6 +496,13 @@ export class WebSocketServer { } } + /** + * Sends formatted message to specific client. + * Handles connection state validation and error logging. + * + * @param clientId - Target client identifier + * @param message - Message to send + */ private sendToClient(clientId: string, message: WSMessage) { const client = this.clients.get(clientId); if (!client || client.ws.readyState !== WebSocket.OPEN) return; @@ -417,7 +511,7 @@ export class WebSocketServer { client.ws.send(JSON.stringify(message)); this.log.debug(`Sent ${message.type} to client ${clientId}`); } catch (error) { - this.log.error(`Failed to send ${message.type} to client ${clientId}:`, error); + this.log.error(`Failed to send ${message.type} to client ${clientId}: %o`, error); } } @@ -473,8 +567,12 @@ export class WebSocketServer { }); } + /** + * Generates a unique subscription identifier using timestamp and random string. + * @returns A unique subscription ID in the format 'sub_[timestamp]_[random]' + */ private generateSubscriptionId(): string { - return `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; } private generateClientId(): string { @@ -491,6 +589,9 @@ export class WebSocketServer { }); } + /** + * Gracefully shuts down the WebSocket server and closes all client connections. + */ async shutdown() { for (const [clientId, client] of this.clients) { this.log.info(`Closing connection to client ${clientId}`); diff --git a/src/services/WebSockets/types.ts b/src/services/WebSockets/types.ts index d6a77b9..11453cc 100644 --- a/src/services/WebSockets/types.ts +++ b/src/services/WebSockets/types.ts @@ -76,6 +76,7 @@ export interface HandshakeMessage extends BaseMessage { export interface SubscribeMessage extends BaseMessage { type: 'SUBSCRIBE'; + data?: string; // Optional data field filters: SubscriptionFilter[]; } @@ -147,7 +148,6 @@ export interface DiscoveryConfig { refreshInterval: number; healthCheckTimeout: number; minArchiveNodes: number; - maxRetries: number; } export interface BlockData { diff --git a/src/utilz/arrayUtil.ts b/src/utilz/arrayUtil.ts index 8c087f2..2c7223c 100644 --- a/src/utilz/arrayUtil.ts +++ b/src/utilz/arrayUtil.ts @@ -33,4 +33,13 @@ export class ArrayUtil { } return Buffer.from(arr0).equals(Buffer.from(arr1)); } + + public static shuffleArray(array: T[]): T[] { + const shuffled = [...array]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + return shuffled; + } } \ No newline at end of file From be644a1d79a85b4dfa4475b43686afd9caf7175d Mon Sep 17 00:00:00 2001 From: rozaso Date: Fri, 31 Jan 2025 06:53:24 -0800 Subject: [PATCH 06/10] Change the network config to earlier. --- .gitignore | 2 +- docker/a.yml | 130 +++++++++++++++++++++++----------------------- docker/cleanup.sh | 12 ++--- docker/evm.yml | 4 +- docker/s.yml | 80 ++++++++++++++-------------- docker/setup.sh | 70 ++++++++++++------------- docker/v.yml | 120 +++++++++++++++++++++--------------------- 7 files changed, 209 insertions(+), 209 deletions(-) diff --git a/.gitignore b/.gitignore index 255fef0..05bc659 100644 --- a/.gitignore +++ b/.gitignore @@ -143,5 +143,5 @@ package-lock.json # devtools secrets package-lock.json /docker/external -/docker/debug-*.sh +/docker/debug-* /log diff --git a/docker/a.yml b/docker/a.yml index 216f796..264eff8 100644 --- a/docker/a.yml +++ b/docker/a.yml @@ -47,74 +47,74 @@ services: - ./_abi/:/config/abi/ - # anode3: - # image: anode-main - # container_name: anode3 - # networks: - # push-dev-network: - # aliases: - # - anode3.local - # environment: - # DB_NAME: anode3 - # PORT: 5003 - # env_file: - # - .env - # - common.env - # - a-specific.env - # ports: - # - "5003:5003" - # entrypoint: ['sh', '/entrypoint.sh'] - # volumes: - # - ./a3:/config - # - ./a3/log:/log - # - ./_abi/:/config/abi/ + anode3: + image: anode-main + container_name: anode3 + networks: + push-dev-network: + aliases: + - anode3.local + environment: + DB_NAME: anode3 + PORT: 5003 + env_file: + - .env + - common.env + - a-specific.env + ports: + - "5003:5003" + entrypoint: ['sh', '/entrypoint.sh'] + volumes: + - ./a3:/config + - ./a3/log:/log + - ./_abi/:/config/abi/ + + + anode4: + image: anode-main + container_name: anode4 + networks: + push-dev-network: + aliases: + - anode4.local + environment: + DB_NAME: anode4 + PORT: 5004 + env_file: + - .env + - common.env + - a-specific.env + ports: + - "5004:5004" + entrypoint: ['sh', '/entrypoint.sh'] + volumes: + - ./a4:/config + - ./a4/log:/log + - ./_abi/:/config/abi/ -# anode4: -# image: anode-main -# container_name: anode4 -# networks: -# push-dev-network: -# aliases: -# - anode4.local -# environment: -# DB_NAME: anode4 -# PORT: 5004 -# env_file: -# - .env -# - common.env -# - a-specific.env -# ports: -# - "5004:5004" -# entrypoint: ['sh', '/entrypoint.sh'] -# volumes: -# - ./a4:/config -# - ./a4/log:/log -# - ./_abi/:/config/abi/ -# -# -# anode5: -# image: anode-main -# container_name: anode5 -# networks: -# push-dev-network: -# aliases: -# - anode5.local -# environment: -# DB_NAME: anode5 -# PORT: 5005 -# env_file: -# - .env -# - common.env -# - a-specific.env -# ports: -# - "5005:5005" -# entrypoint: ['sh', '/entrypoint.sh'] -# volumes: -# - ./a5:/config -# - ./a5/log:/log -# - ./_abi/:/config/abi/ + anode5: + image: anode-main + container_name: anode5 + networks: + push-dev-network: + aliases: + - anode5.local + environment: + DB_NAME: anode5 + PORT: 5005 + env_file: + - .env + - common.env + - a-specific.env + ports: + - "5005:5005" + entrypoint: ['sh', '/entrypoint.sh'] + volumes: + - ./a5:/config + - ./a5/log:/log + - ./_abi/:/config/abi/ diff --git a/docker/cleanup.sh b/docker/cleanup.sh index b75be29..27ed0ed 100755 --- a/docker/cleanup.sh +++ b/docker/cleanup.sh @@ -11,15 +11,15 @@ esac echo "starting cleanup" -# echo "removing s nodes" -# docker compose -f s.yml down +echo "removing s nodes" +docker compose -f s.yml down -# echo "removing v nodes" -# docker compose -f v.yml down +echo "removing v nodes" +docker compose -f v.yml down -# echo "removing a nodes" -# docker compose -f a.yml down +echo "removing a nodes" +docker compose -f a.yml down echo "removing evm" diff --git a/docker/evm.yml b/docker/evm.yml index ca3a223..15e2339 100644 --- a/docker/evm.yml +++ b/docker/evm.yml @@ -18,8 +18,8 @@ services: # remove everything: ./cleanup.sh # uncomment N number of records in v.yml ; s.yml; a.yml which matches the values above # re-create everything: ./setup.sh - VNODE_COUNT: 3 # max 10 - SNODE_COUNT: 1 # max 8 + VNODE_COUNT: 6 # max 10 + SNODE_COUNT: 3 # max 8 ANODE_COUNT: 3 # max 5 diff --git a/docker/s.yml b/docker/s.yml index 7be268a..22ff160 100644 --- a/docker/s.yml +++ b/docker/s.yml @@ -24,48 +24,48 @@ services: - ./_abi/:/config/abi/ - # snode2: - # image: snode-main - # container_name: snode2 - # networks: - # push-dev-network: - # aliases: - # - snode2.local - # environment: - # DB_NAME: snode2 - # PORT: 3002 - # env_file: - # - .env - # - common.env - # - s-specific.env - # ports: - # - "3002:3002" - # volumes: - # - ./s2:/config - # - ./s2/log:/log - # - ./_abi/:/config/abi/ + snode2: + image: snode-main + container_name: snode2 + networks: + push-dev-network: + aliases: + - snode2.local + environment: + DB_NAME: snode2 + PORT: 3002 + env_file: + - .env + - common.env + - s-specific.env + ports: + - "3002:3002" + volumes: + - ./s2:/config + - ./s2/log:/log + - ./_abi/:/config/abi/ - # snode3: - # image: snode-main - # container_name: snode3 - # networks: - # push-dev-network: - # aliases: - # - snode3.local - # environment: - # DB_NAME: snode3 - # PORT: 3003 - # env_file: - # - .env - # - common.env - # - s-specific.env - # ports: - # - "3003:3003" - # volumes: - # - ./s3:/config - # - ./s3/log:/log - # - ./_abi/:/config/abi/ + snode3: + image: snode-main + container_name: snode3 + networks: + push-dev-network: + aliases: + - snode3.local + environment: + DB_NAME: snode3 + PORT: 3003 + env_file: + - .env + - common.env + - s-specific.env + ports: + - "3003:3003" + volumes: + - ./s3:/config + - ./s3/log:/log + - ./_abi/:/config/abi/ # snode4: diff --git a/docker/setup.sh b/docker/setup.sh index 21b5bfa..caccda7 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -40,43 +40,43 @@ fi echo -# echo "---- STARTING VNODE" -# read -p "-> Press ENTER to continue, or 'n' to skip: " response -# if [[ "$response" == "n" || "$response" == "N" ]]; then -# echo "Skipped" -# else -# echo -# cd "${CHAIN_DIR}/push-vnode" || exit 1 -# docker build . -t vnode-main -# cd "${DOCKER_DIR}" || exit 1 -# docker compose -f v.yml down && docker compose -f v.yml up -d && docker compose -f v.yml logs -f -# fi +echo "---- STARTING VNODE" +read -p "-> Press ENTER to continue, or 'n' to skip: " response +if [[ "$response" == "n" || "$response" == "N" ]]; then + echo "Skipped" +else + echo + cd "${CHAIN_DIR}/push-vnode" || exit 1 + docker build . -t vnode-main + cd "${DOCKER_DIR}" || exit 1 + docker compose -f v.yml down && docker compose -f v.yml up -d && docker compose -f v.yml logs -f +fi -# echo -# echo "---- STARTING SNODE" -# read -p "-> Press ENTER to continue, or 'n' to skip: " response -# if [[ "$response" == "n" || "$response" == "N" ]]; then -# echo "Skipped" -# else -# echo -# cd "${CHAIN_DIR}/push-snode" || exit 1 -# docker build . -t snode-main -# cd "${DOCKER_DIR}" || exit 1 -# docker compose -f s.yml down && docker compose -f s.yml up -d && docker compose -f s.yml logs -f -# fi +echo +echo "---- STARTING SNODE" +read -p "-> Press ENTER to continue, or 'n' to skip: " response +if [[ "$response" == "n" || "$response" == "N" ]]; then + echo "Skipped" +else + echo + cd "${CHAIN_DIR}/push-snode" || exit 1 + docker build . -t snode-main + cd "${DOCKER_DIR}" || exit 1 + docker compose -f s.yml down && docker compose -f s.yml up -d && docker compose -f s.yml logs -f +fi -# echo -# echo "---- STARTING ANODE" -# read -p "-> Press ENTER to continue, or 'n' to skip: " response -# if [[ "$response" == "n" || "$response" == "N" ]]; then -# echo "Skipped" -# else -# echo -# cd "${CHAIN_DIR}/push-anode" || exit 1 -# docker build -t anode-main -f Dockerfile.light . -# cd "${DOCKER_DIR}" || exit 1 -# docker compose -f a.yml down && docker compose -f a.yml up -d && docker compose -f a.yml logs -f -# fi +echo +echo "---- STARTING ANODE" +read -p "-> Press ENTER to continue, or 'n' to skip: " response +if [[ "$response" == "n" || "$response" == "N" ]]; then + echo "Skipped" +else + echo + cd "${CHAIN_DIR}/push-anode" || exit 1 + docker build -t anode-main -f Dockerfile.light . + cd "${DOCKER_DIR}" || exit 1 + docker compose -f a.yml down && docker compose -f a.yml up -d && docker compose -f a.yml logs -f +fi diff --git a/docker/v.yml b/docker/v.yml index ad563f2..ae370c8 100644 --- a/docker/v.yml +++ b/docker/v.yml @@ -67,68 +67,68 @@ services: - ./_abi/:/config/abi/ - # vnode4: - # image: vnode-main - # container_name: vnode4 - # networks: - # push-dev-network: - # aliases: - # - vnode4.local - # environment: - # DB_NAME: vnode4 - # PORT: 4004 - # env_file: - # - .env - # - common.env - # - v-specific.env - # ports: - # - "4004:4004" - # volumes: - # - ./v4:/config - # - ./v4/log:/log - # - ./_abi/:/config/abi/ + vnode4: + image: vnode-main + container_name: vnode4 + networks: + push-dev-network: + aliases: + - vnode4.local + environment: + DB_NAME: vnode4 + PORT: 4004 + env_file: + - .env + - common.env + - v-specific.env + ports: + - "4004:4004" + volumes: + - ./v4:/config + - ./v4/log:/log + - ./_abi/:/config/abi/ - # vnode5: - # image: vnode-main - # container_name: vnode5 - # networks: - # push-dev-network: - # aliases: - # - vnode5.local - # environment: - # DB_NAME: vnode5 - # PORT: 4005 - # env_file: - # - .env - # - common.env - # - v-specific.env - # ports: - # - "4005:4005" - # volumes: - # - ./v5:/config - # - ./v5/log:/log - # - ./_abi/:/config/abi/ + vnode5: + image: vnode-main + container_name: vnode5 + networks: + push-dev-network: + aliases: + - vnode5.local + environment: + DB_NAME: vnode5 + PORT: 4005 + env_file: + - .env + - common.env + - v-specific.env + ports: + - "4005:4005" + volumes: + - ./v5:/config + - ./v5/log:/log + - ./_abi/:/config/abi/ - # vnode6: - # image: vnode-main - # container_name: vnode6 - # networks: - # push-dev-network: - # aliases: - # - vnode6.local - # environment: - # DB_NAME: vnode6 - # PORT: 4006 - # env_file: - # - .env - # - common.env - # - v-specific.env - # ports: - # - "4006:4006" - # volumes: - # - ./v6:/config - # - ./v6/log:/log - # - ./_abi/:/config/abi/ + vnode6: + image: vnode-main + container_name: vnode6 + networks: + push-dev-network: + aliases: + - vnode6.local + environment: + DB_NAME: vnode6 + PORT: 4006 + env_file: + - .env + - common.env + - v-specific.env + ports: + - "4006:4006" + volumes: + - ./v6:/config + - ./v6/log:/log + - ./_abi/:/config/abi/ # vnode7: # image: vnode-main From e9cd450609f3375ae7c0ae26511839409d307247 Mon Sep 17 00:00:00 2001 From: rozaso Date: Fri, 31 Jan 2025 06:55:41 -0800 Subject: [PATCH 07/10] Change back the a.yml config as per the network comnfig. --- docker/a.yml | 84 ++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/docker/a.yml b/docker/a.yml index 264eff8..f4904cd 100644 --- a/docker/a.yml +++ b/docker/a.yml @@ -71,50 +71,50 @@ services: - anode4: - image: anode-main - container_name: anode4 - networks: - push-dev-network: - aliases: - - anode4.local - environment: - DB_NAME: anode4 - PORT: 5004 - env_file: - - .env - - common.env - - a-specific.env - ports: - - "5004:5004" - entrypoint: ['sh', '/entrypoint.sh'] - volumes: - - ./a4:/config - - ./a4/log:/log - - ./_abi/:/config/abi/ +# anode4: +# image: anode-main +# container_name: anode4 +# networks: +# push-dev-network: +# aliases: +# - anode4.local +# environment: +# DB_NAME: anode4 +# PORT: 5004 +# env_file: +# - .env +# - common.env +# - a-specific.env +# ports: +# - "5004:5004" +# entrypoint: ['sh', '/entrypoint.sh'] +# volumes: +# - ./a4:/config +# - ./a4/log:/log +# - ./_abi/:/config/abi/ - anode5: - image: anode-main - container_name: anode5 - networks: - push-dev-network: - aliases: - - anode5.local - environment: - DB_NAME: anode5 - PORT: 5005 - env_file: - - .env - - common.env - - a-specific.env - ports: - - "5005:5005" - entrypoint: ['sh', '/entrypoint.sh'] - volumes: - - ./a5:/config - - ./a5/log:/log - - ./_abi/:/config/abi/ +# anode5: +# image: anode-main +# container_name: anode5 +# networks: +# push-dev-network: +# aliases: +# - anode5.local +# environment: +# DB_NAME: anode5 +# PORT: 5005 +# env_file: +# - .env +# - common.env +# - a-specific.env +# ports: +# - "5005:5005" +# entrypoint: ['sh', '/entrypoint.sh'] +# volumes: +# - ./a5:/config +# - ./a5/log:/log +# - ./_abi/:/config/abi/ From 08ee8bf217cfb396342e4694d271218a70975195 Mon Sep 17 00:00:00 2001 From: rozaso Date: Fri, 31 Jan 2025 07:00:58 -0800 Subject: [PATCH 08/10] Config changes for types. --- docker/a.yml | 3 ++- tsconfig.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/a.yml b/docker/a.yml index f4904cd..49bca56 100644 --- a/docker/a.yml +++ b/docker/a.yml @@ -93,7 +93,8 @@ services: # - ./a4/log:/log # - ./_abi/:/config/abi/ - +# +# # anode5: # image: anode-main # container_name: anode5 diff --git a/tsconfig.json b/tsconfig.json index 1c5f775..6a138d4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "target": "es2017", "lib": ["es2017", "esnext.asynciterable"], - "typeRoots": ["./node_modules/@types", "src/services/WebSockets/types", "./src/generated"], + "typeRoots": ["./node_modules/@types", "./src/types", "./src/services/WebSockets/types", "./src/generated"], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, From aa68d1d23f52bff482259106fc3e8c36171e9051 Mon Sep 17 00:00:00 2001 From: rozaso Date: Fri, 31 Jan 2025 07:02:31 -0800 Subject: [PATCH 09/10] Formatting. --- docker/a.yml | 1 - tsconfig.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/a.yml b/docker/a.yml index 49bca56..21e2836 100644 --- a/docker/a.yml +++ b/docker/a.yml @@ -92,7 +92,6 @@ services: # - ./a4:/config # - ./a4/log:/log # - ./_abi/:/config/abi/ - # # # anode5: diff --git a/tsconfig.json b/tsconfig.json index 6a138d4..e9ea117 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "target": "es2017", "lib": ["es2017", "esnext.asynciterable"], - "typeRoots": ["./node_modules/@types", "./src/types", "./src/services/WebSockets/types", "./src/generated"], + "typeRoots": ["./node_modules/@types", "./src/types", "./src/services/WebSockets/types", "./src/generated"], "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, From 6ecf2b3aeb913b6e26e24169b39fbfb9bc1e274b Mon Sep 17 00:00:00 2001 From: rozaso Date: Fri, 31 Jan 2025 09:16:28 -0800 Subject: [PATCH 10/10] Correct reading the config. --- src/services/WebSockets/WebSocketClient.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/WebSockets/WebSocketClient.ts b/src/services/WebSockets/WebSocketClient.ts index e159db5..3ac59e3 100644 --- a/src/services/WebSockets/WebSocketClient.ts +++ b/src/services/WebSockets/WebSocketClient.ts @@ -16,7 +16,6 @@ import { Wallet } from 'ethers'; */ @Service() export class WebSocketClient { - private readonly MIN_ARCHIVE_CONNECTIONS = 1; private archiveConnections = new Map(); private readonly log = WinstonUtil.newLog("WebSocketClient"); private reconnectTimer: NodeJS.Timer; @@ -478,4 +477,11 @@ export class WebSocketClient { }; this.connectionStates.set(nodeId, { ...current, ...update }); } + + /** + * Gets the minimum required archive node connections from discovery service + */ + private get MIN_ARCHIVE_CONNECTIONS(): number { + return this.discoveryService.getMinArchiveNodes(); + } } \ No newline at end of file