diff --git a/backend/src/api/loading-indicators.ts b/backend/src/api/loading-indicators.ts new file mode 100644 index 000000000..c2d682d1c --- /dev/null +++ b/backend/src/api/loading-indicators.ts @@ -0,0 +1,32 @@ +import { ILoadingIndicators } from '../mempool.interfaces'; + +class LoadingIndicators { + private loadingIndicators: ILoadingIndicators = { + 'mempool': 0, + }; + private progressChangedCallback: ((loadingIndicators: ILoadingIndicators) => void) | undefined; + + constructor() { } + + public setProgressChangedCallback(fn: (loadingIndicators: ILoadingIndicators) => void) { + this.progressChangedCallback = fn; + } + + public setProgress(name: string, progressPercent: number) { + const newProgress = Math.round(progressPercent); + if (newProgress >= 100) { + delete this.loadingIndicators[name]; + } else { + this.loadingIndicators[name] = newProgress; + } + if (this.progressChangedCallback) { + this.progressChangedCallback(this.loadingIndicators); + } + } + + public getLoadingIndicators() { + return this.loadingIndicators; + } +} + +export default new LoadingIndicators(); diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 7f066f1a6..27cd94794 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -6,6 +6,7 @@ import { Common } from './common'; import transactionUtils from './transaction-utils'; import { IBitcoinApi } from './bitcoin/bitcoin-api.interface'; import bitcoinBaseApi from './bitcoin/bitcoin-base.api'; +import loadingIndicators from './loading-indicators'; class Mempool { private inSync: boolean = false; @@ -90,6 +91,10 @@ class Mempool { const diff = transactions.length - currentMempoolSize; const newTransactions: TransactionExtended[] = []; + if (!this.inSync) { + loadingIndicators.setProgress('mempool', Object.keys(this.mempoolCache).length / transactions.length * 100); + } + for (const txid of transactions) { if (!this.mempoolCache[txid]) { const transaction = await transactionUtils.$getTransactionExtended(txid, true); @@ -162,6 +167,7 @@ class Mempool { if (!this.inSync && transactions.length === Object.keys(newMempool).length) { this.inSync = true; logger.info('The mempool is now in sync!'); + loadingIndicators.setProgress('mempool', 100); } if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) { diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 898cca125..2d4e75d6f 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -1,12 +1,13 @@ import logger from '../logger'; import * as WebSocket from 'ws'; -import { BlockExtended, TransactionExtended, WebsocketResponse, MempoolBlock, OptimizedStatistic } from '../mempool.interfaces'; +import { BlockExtended, TransactionExtended, WebsocketResponse, MempoolBlock, OptimizedStatistic, ILoadingIndicators } from '../mempool.interfaces'; import blocks from './blocks'; import memPool from './mempool'; import backendInfo from './backend-info'; import mempoolBlocks from './mempool-blocks'; import fiatConversion from './fiat-conversion'; import { Common } from './common'; +import loadingIndicators from './loading-indicators'; class WebsocketHandler { private wss: WebSocket.Server | undefined; @@ -117,6 +118,19 @@ class WebsocketHandler { }); } + handleLoadingChanged(indicators: ILoadingIndicators) { + if (!this.wss) { + throw new Error('WebSocket.Server is not set'); + } + + this.wss.clients.forEach((client: WebSocket) => { + if (client.readyState !== WebSocket.OPEN) { + return; + } + client.send(JSON.stringify({ loadingIndicators: indicators })); + }); + } + getInitData(_blocks?: BlockExtended[]) { if (!_blocks) { _blocks = blocks.getBlocks().slice(-8); @@ -131,6 +145,7 @@ class WebsocketHandler { 'transactions': memPool.getLatestTransactions(), 'git-commit': backendInfo.gitCommitHash, 'hostname': backendInfo.hostname, + 'loadingIndicators': loadingIndicators.getLoadingIndicators(), ...this.extraInitProperties }; } diff --git a/backend/src/index.ts b/backend/src/index.ts index 4c753fea7..d2db74b74 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -20,6 +20,7 @@ import bisqMarkets from './api/bisq/markets'; import donations from './api/donations'; import logger from './logger'; import backendInfo from './api/backend-info'; +import loadingIndicators from './api/loading-indicators'; class Server { private wss: WebSocket.Server | undefined; @@ -135,6 +136,7 @@ class Server { blocks.setNewBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler)); memPool.setMempoolChangedCallback(websocketHandler.handleMempoolChange.bind(websocketHandler)); donations.setNotfyDonationStatusCallback(websocketHandler.handleNewDonation.bind(websocketHandler)); + loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler)); } setUpHttpApiRoutes() { diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 1058df7f0..34d1bf6b6 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -136,3 +136,4 @@ interface RequiredParams { types: ('@string' | '@number' | '@boolean' | string)[]; } +export interface ILoadingIndicators { [name: string]: number; } diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index dcc7df9dd..a78092376 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -179,8 +179,8 @@
Incoming transactions
- -  Backend is synchronizing + +  Backend is synchronizing ({{ mempoolLoadingStatus$ | async }}%)
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index 6e419712e..6f573aac2 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -50,6 +50,7 @@ export class DashboardComponent implements OnInit { mempoolBlocksData$: Observable; mempoolInfoData$: Observable; difficultyEpoch$: Observable; + mempoolLoadingStatus$: Observable; vBytesPerSecondLimit = 1667; blocks$: Observable; transactions$: Observable; @@ -77,6 +78,9 @@ export class DashboardComponent implements OnInit { this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); this.network$ = merge(of(''), this.stateService.networkChanged$); this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one'; + this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$.pipe( + map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100) + ); this.languageForm = this.formBuilder.group({ language: [''] diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 893dc1f99..e6d21267b 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -1,3 +1,4 @@ +import { ILoadingIndicators } from '../services/state.service'; import { Block, Transaction } from './electrs.interface'; export interface WebsocketResponse { @@ -15,6 +16,7 @@ export interface WebsocketResponse { rbfTransaction?: Transaction; transactions?: TransactionStripped[]; donationConfirmed?: boolean; + loadingIndicators?: ILoadingIndicators; 'track-tx'?: string; 'track-address'?: string; 'track-asset'?: string; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 29b501f1e..289a2caa0 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -13,6 +13,8 @@ interface MarkBlockState { txFeePerVSize?: number; } +export interface ILoadingIndicators { [name: string]: number; } + export interface Env { TESTNET_ENABLED: boolean; LIQUID_ENABLED: boolean; @@ -63,6 +65,7 @@ export class StateService { lastDifficultyAdjustment$ = new ReplaySubject(1); gitCommit$ = new ReplaySubject(1); donationConfirmed$ = new Subject(); + loadingIndicators$ = new ReplaySubject(1); live2Chart$ = new Subject(); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 71bbe5263..66bb70b54 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -270,6 +270,10 @@ export class WebsocketService { this.stateService.live2Chart$.next(response['live-2h-chart']); } + if (response.loadingIndicators) { + this.stateService.loadingIndicators$.next(response.loadingIndicators); + } + if (response.mempoolInfo) { this.stateService.mempoolInfo$.next(response.mempoolInfo); }