Merge remote-tracking branch 'origin/master' into mononaut/seo-ssr
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
|
||||
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights } from '../interfaces/node-api.interface';
|
||||
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit } from '../interfaces/node-api.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
import { Outspend, Transaction } from '../interfaces/electrs.interface';
|
||||
import { Conversion } from './price.service';
|
||||
|
||||
const SERVICES_API_PREFIX = `/api/v1/services`;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@@ -72,6 +74,10 @@ export class ApiService {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/4y');
|
||||
}
|
||||
|
||||
listAllTimeStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/all');
|
||||
}
|
||||
|
||||
getTransactionTimes$(txIds: string[]): Observable<number[]> {
|
||||
let params = new HttpParams();
|
||||
txIds.forEach((txId: string) => {
|
||||
@@ -88,15 +94,11 @@ export class ApiService {
|
||||
return this.httpClient.get<Outspend[][]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/outspends', { params });
|
||||
}
|
||||
|
||||
requestDonation$(amount: number, orderId: string): Observable<any> {
|
||||
const params = {
|
||||
amount: amount,
|
||||
orderId: orderId,
|
||||
};
|
||||
return this.httpClient.post<any>(this.apiBaseUrl + '/api/v1/donations', params);
|
||||
getAboutPageProfiles$(): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/about-page');
|
||||
}
|
||||
|
||||
getDonation$(): Observable<any[]> {
|
||||
getOgs$(): Observable<any> {
|
||||
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/donations');
|
||||
}
|
||||
|
||||
@@ -108,10 +110,6 @@ export class ApiService {
|
||||
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/contributors');
|
||||
}
|
||||
|
||||
checkDonation$(orderId: string): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/donations/check?order_id=' + orderId);
|
||||
}
|
||||
|
||||
getInitData$(): Observable<WebsocketResponse> {
|
||||
return this.httpClient.get<WebsocketResponse>(this.apiBaseUrl + this.apiBasePath + '/api/v1/init-data');
|
||||
}
|
||||
@@ -124,14 +122,18 @@ export class ApiService {
|
||||
return this.httpClient.get<AddressInformation>(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address);
|
||||
}
|
||||
|
||||
getRbfHistory$(txid: string): Observable<string[]> {
|
||||
return this.httpClient.get<string[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/replaces');
|
||||
getRbfHistory$(txid: string): Observable<{ replacements: RbfTree, replaces: string[] }> {
|
||||
return this.httpClient.get<{ replacements: RbfTree, replaces: string[] }>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/rbf');
|
||||
}
|
||||
|
||||
getRbfCachedTx$(txid: string): Observable<Transaction> {
|
||||
return this.httpClient.get<Transaction>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/cached');
|
||||
}
|
||||
|
||||
getRbfList$(fullRbf: boolean, after?: string): Observable<RbfTree[]> {
|
||||
return this.httpClient.get<RbfTree[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/' + (fullRbf ? 'fullrbf/' : '') + 'replacements/' + (after || ''));
|
||||
}
|
||||
|
||||
listLiquidPegsMonth$(): Observable<LiquidPegs[]> {
|
||||
return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
|
||||
}
|
||||
@@ -234,16 +236,16 @@ export class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
getHistoricalBlockPrediction$(interval: string | undefined) : Observable<any> {
|
||||
getHistoricalBlocksHealth$(interval: string | undefined) : Observable<any> {
|
||||
return this.httpClient.get<any[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/predictions` +
|
||||
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||
);
|
||||
}
|
||||
|
||||
getBlockAudit$(hash: string) : Observable<any> {
|
||||
return this.httpClient.get<any>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/block/${hash}/audit-summary`, { observe: 'response' }
|
||||
getBlockAudit$(hash: string) : Observable<BlockAudit> {
|
||||
return this.httpClient.get<BlockAudit>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/block/${hash}/audit-summary`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -315,4 +317,13 @@ export class ApiService {
|
||||
(timestamp ? `?timestamp=${timestamp}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
getNodeOwner$(publicKey: string) {
|
||||
let params = new HttpParams()
|
||||
.set('node_public_key', publicKey);
|
||||
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/lightning/claim/current`, { params, observe: 'response' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export class CacheService {
|
||||
txCache: { [txid: string]: Transaction } = {};
|
||||
|
||||
network: string;
|
||||
blockHashCache: { [hash: string]: BlockExtended } = {};
|
||||
blockCache: { [height: number]: BlockExtended } = {};
|
||||
blockLoading: { [height: number]: boolean } = {};
|
||||
copiesInBlockQueue: { [height: number]: number } = {};
|
||||
@@ -27,8 +28,10 @@ export class CacheService {
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
) {
|
||||
this.stateService.blocks$.subscribe(([block]) => {
|
||||
this.addBlockToCache(block);
|
||||
this.stateService.blocks$.subscribe((blocks) => {
|
||||
for (const block of blocks) {
|
||||
this.addBlockToCache(block);
|
||||
}
|
||||
this.clearBlocks();
|
||||
});
|
||||
this.stateService.chainTip$.subscribe((height) => {
|
||||
@@ -56,8 +59,11 @@ export class CacheService {
|
||||
}
|
||||
|
||||
addBlockToCache(block: BlockExtended) {
|
||||
this.blockCache[block.height] = block;
|
||||
this.bumpBlockPriority(block.height);
|
||||
if (!this.blockHashCache[block.id]) {
|
||||
this.blockHashCache[block.id] = block;
|
||||
this.blockCache[block.height] = block;
|
||||
this.bumpBlockPriority(block.height);
|
||||
}
|
||||
}
|
||||
|
||||
async loadBlock(height) {
|
||||
@@ -105,7 +111,9 @@ export class CacheService {
|
||||
} else if ((this.tip - height) < KEEP_RECENT_BLOCKS) {
|
||||
this.bumpBlockPriority(height);
|
||||
} else {
|
||||
const block = this.blockCache[height];
|
||||
delete this.blockCache[height];
|
||||
delete this.blockHashCache[block.id];
|
||||
delete this.copiesInBlockQueue[height];
|
||||
}
|
||||
}
|
||||
@@ -113,6 +121,7 @@ export class CacheService {
|
||||
|
||||
// remove all blocks from the cache
|
||||
resetBlockCache() {
|
||||
this.blockHashCache = {};
|
||||
this.blockCache = {};
|
||||
this.blockLoading = {};
|
||||
this.copiesInBlockQueue = {};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable, from, of, switchMap } from 'rxjs';
|
||||
import { Transaction, Address, Outspend, Recent, Asset, ScriptHash } from '../interfaces/electrs.interface';
|
||||
import { StateService } from './state.service';
|
||||
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||
import { calcScriptHash$ } from '../bitcoin.utils';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -65,12 +66,41 @@ export class ElectrsApiService {
|
||||
return this.httpClient.get<Address>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address);
|
||||
}
|
||||
|
||||
getAddressTransactions$(address: string): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs');
|
||||
getPubKeyAddress$(pubkey: string): Observable<Address> {
|
||||
const scriptpubkey = (pubkey.length === 130 ? '41' : '21') + pubkey + 'ac';
|
||||
return this.getScriptHash$(scriptpubkey).pipe(
|
||||
switchMap((scripthash: ScriptHash) => {
|
||||
return of({
|
||||
...scripthash,
|
||||
address: pubkey,
|
||||
is_pubkey: true,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getAddressTransactionsFromHash$(address: string, txid: string): Observable<Transaction[]> {
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs/chain/' + txid);
|
||||
getScriptHash$(script: string): Observable<ScriptHash> {
|
||||
return from(calcScriptHash$(script)).pipe(
|
||||
switchMap(scriptHash => this.httpClient.get<ScriptHash>(this.apiBaseUrl + this.apiBasePath + '/api/scripthash/' + scriptHash))
|
||||
);
|
||||
}
|
||||
|
||||
getAddressTransactions$(address: string, txid?: string): Observable<Transaction[]> {
|
||||
let params = new HttpParams();
|
||||
if (txid) {
|
||||
params = params.append('after_txid', txid);
|
||||
}
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs', { params });
|
||||
}
|
||||
|
||||
getScriptHashTransactions$(script: string, txid?: string): Observable<Transaction[]> {
|
||||
let params = new HttpParams();
|
||||
if (txid) {
|
||||
params = params.append('after_txid', txid);
|
||||
}
|
||||
return from(calcScriptHash$(script)).pipe(
|
||||
switchMap(scriptHash => this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/scripthash/' + scriptHash + '/txs', { params })),
|
||||
);
|
||||
}
|
||||
|
||||
getAsset$(assetId: string): Observable<Asset> {
|
||||
|
||||
@@ -96,7 +96,7 @@ export class MiningService {
|
||||
share: parseFloat((poolStat.blockCount / stats.blockCount * 100).toFixed(2)),
|
||||
lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
|
||||
emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
|
||||
logo: `/resources/mining-pools/` + poolStat.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg',
|
||||
logo: `/resources/mining-pools/` + poolStat.slug + '.svg',
|
||||
...poolStat
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { BlockExtended, DifficultyAdjustment, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
||||
import { StorageService } from './storage.service';
|
||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
||||
|
||||
interface MarkBlockState {
|
||||
export interface MarkBlockState {
|
||||
blockHeight?: number;
|
||||
txid?: string;
|
||||
mempoolBlockIndex?: number;
|
||||
txFeePerVSize?: number;
|
||||
mempoolPosition?: MempoolPosition;
|
||||
}
|
||||
|
||||
export interface ILoadingIndicators { [name: string]: number; }
|
||||
@@ -44,6 +47,7 @@ export interface Env {
|
||||
TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||
HISTORICAL_PRICE: boolean;
|
||||
ACCELERATOR: boolean;
|
||||
}
|
||||
|
||||
const defaultEnv: Env = {
|
||||
@@ -74,6 +78,7 @@ const defaultEnv: Env = {
|
||||
'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||
'HISTORICAL_PRICE': true,
|
||||
'ACCELERATOR': false,
|
||||
};
|
||||
|
||||
@Injectable({
|
||||
@@ -86,10 +91,12 @@ export class StateService {
|
||||
blockVSize: number;
|
||||
env: Env;
|
||||
latestBlockHeight = -1;
|
||||
blocks: BlockExtended[] = [];
|
||||
|
||||
networkChanged$ = new ReplaySubject<string>(1);
|
||||
lightningChanged$ = new ReplaySubject<boolean>(1);
|
||||
blocks$: ReplaySubject<[BlockExtended, boolean]>;
|
||||
blocksSubject$ = new BehaviorSubject<BlockExtended[]>([]);
|
||||
blocks$: Observable<BlockExtended[]>;
|
||||
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
||||
conversions$ = new ReplaySubject<any>(1);
|
||||
bsqPrice$ = new ReplaySubject<number>(1);
|
||||
@@ -97,12 +104,19 @@ export class StateService {
|
||||
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
||||
mempoolBlockTransactions$ = new Subject<TransactionStripped[]>();
|
||||
mempoolBlockDelta$ = new Subject<MempoolBlockDelta>();
|
||||
liveMempoolBlockTransactions$: Observable<{ [txid: string]: TransactionStripped}>;
|
||||
txConfirmed$ = new Subject<[string, BlockExtended]>();
|
||||
txReplaced$ = new Subject<ReplacedTransaction>();
|
||||
txRbfInfo$ = new Subject<RbfTree>();
|
||||
rbfLatest$ = new Subject<RbfTree[]>();
|
||||
rbfLatestSummary$ = new Subject<ReplacementInfo[]>();
|
||||
utxoSpent$ = new Subject<object>();
|
||||
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
||||
mempoolTransactions$ = new Subject<Transaction>();
|
||||
mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition}>();
|
||||
blockTransactions$ = new Subject<Transaction>();
|
||||
isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
|
||||
isLoadingMempool$ = new BehaviorSubject<boolean>(true);
|
||||
vbytesPerSecond$ = new ReplaySubject<number>(1);
|
||||
previousRetarget$ = new ReplaySubject<number>(1);
|
||||
backendInfo$ = new ReplaySubject<IBackendInfo>(1);
|
||||
@@ -118,12 +132,17 @@ export class StateService {
|
||||
|
||||
markBlock$ = new BehaviorSubject<MarkBlockState>({});
|
||||
keyNavigation$ = new Subject<KeyboardEvent>();
|
||||
searchText$ = new BehaviorSubject<string>('');
|
||||
|
||||
blockScrolling$: Subject<boolean> = new Subject<boolean>();
|
||||
resetScroll$: Subject<boolean> = new Subject<boolean>();
|
||||
timeLtr: BehaviorSubject<boolean>;
|
||||
hideFlow: BehaviorSubject<boolean>;
|
||||
hideAudit: BehaviorSubject<boolean>;
|
||||
fiatCurrency$: BehaviorSubject<string>;
|
||||
rateUnits$: BehaviorSubject<string>;
|
||||
|
||||
searchFocus$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
@Inject(PLATFORM_ID) private platformId: any,
|
||||
@@ -157,15 +176,44 @@ export class StateService {
|
||||
}
|
||||
});
|
||||
|
||||
this.blocks$ = new ReplaySubject<[BlockExtended, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.liveMempoolBlockTransactions$ = merge(
|
||||
this.mempoolBlockTransactions$.pipe(map(transactions => { return { transactions }; })),
|
||||
this.mempoolBlockDelta$.pipe(map(delta => { return { delta }; })),
|
||||
).pipe(scan((transactions: { [txid: string]: TransactionStripped }, change: any): { [txid: string]: TransactionStripped } => {
|
||||
if (change.transactions) {
|
||||
const txMap = {}
|
||||
change.transactions.forEach(tx => {
|
||||
txMap[tx.txid] = tx;
|
||||
})
|
||||
return txMap;
|
||||
} else {
|
||||
change.delta.changed.forEach(tx => {
|
||||
transactions[tx.txid].rate = tx.rate;
|
||||
})
|
||||
change.delta.removed.forEach(txid => {
|
||||
delete transactions[txid];
|
||||
});
|
||||
change.delta.added.forEach(tx => {
|
||||
transactions[tx.txid] = tx;
|
||||
});
|
||||
return transactions;
|
||||
}
|
||||
}, {}));
|
||||
|
||||
if (this.env.BASE_MODULE === 'bisq') {
|
||||
this.network = this.env.BASE_MODULE;
|
||||
this.networkChanged$.next(this.env.BASE_MODULE);
|
||||
}
|
||||
|
||||
this.networkChanged$.subscribe((network) => {
|
||||
this.transactions$ = new ReplaySubject<TransactionStripped>(6);
|
||||
this.blocksSubject$.next([]);
|
||||
});
|
||||
|
||||
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
|
||||
|
||||
this.blocks$ = this.blocksSubject$.pipe(filter(blocks => blocks != null && blocks.length > 0));
|
||||
|
||||
const savedTimePreference = this.storageService.getValue('time-preference-ltr');
|
||||
const rtlLanguage = (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he'));
|
||||
// default time direction is right-to-left, unless locale is a RTL language
|
||||
@@ -192,6 +240,9 @@ export class StateService {
|
||||
|
||||
const fiatPreference = this.storageService.getValue('fiat-preference');
|
||||
this.fiatCurrency$ = new BehaviorSubject<string>(fiatPreference || 'USD');
|
||||
|
||||
const rateUnitPreference = this.storageService.getValue('rate-unit-preference');
|
||||
this.rateUnits$ = new BehaviorSubject<string>(rateUnitPreference || 'vb');
|
||||
}
|
||||
|
||||
setNetworkBasedonUrl(url: string) {
|
||||
@@ -299,4 +350,21 @@ export class StateService {
|
||||
this.chainTip$.next(height);
|
||||
}
|
||||
}
|
||||
|
||||
resetBlocks(blocks: BlockExtended[]): void {
|
||||
this.blocks = blocks.reverse();
|
||||
this.blocksSubject$.next(blocks);
|
||||
}
|
||||
|
||||
addBlock(block: BlockExtended): void {
|
||||
this.blocks.unshift(block);
|
||||
this.blocks = this.blocks.slice(0, this.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.blocksSubject$.next(this.blocks);
|
||||
}
|
||||
|
||||
focusSearchInputDesktop() {
|
||||
if (!hasTouchScreen()) {
|
||||
this.searchFocus$.next(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,22 @@ export class StorageService {
|
||||
|
||||
setDefaultValueIfNeeded(key: string, defaultValue: string) {
|
||||
const graphWindowPreference: string = this.getValue(key);
|
||||
const fragment = window.location.hash.replace('#', '');
|
||||
|
||||
if (graphWindowPreference === null) { // First visit to mempool.space
|
||||
if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
|
||||
this.router.url.includes('pools') && key === 'miningWindowPreference'
|
||||
if (window.location.pathname.includes('graphs') && key === 'graphWindowPreference' ||
|
||||
window.location.pathname.includes('pools') && key === 'miningWindowPreference'
|
||||
) {
|
||||
this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue);
|
||||
this.setValue(key, fragment ? fragment : defaultValue);
|
||||
} else {
|
||||
this.setValue(key, defaultValue);
|
||||
}
|
||||
} else if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
|
||||
this.router.url.includes('pools') && key === 'miningWindowPreference'
|
||||
} else if (window.location.pathname.includes('graphs') && key === 'graphWindowPreference' ||
|
||||
window.location.pathname.includes('pools') && key === 'miningWindowPreference'
|
||||
) {
|
||||
// Visit a different graphs#fragment from last visit
|
||||
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
|
||||
this.setValue(key, this.route.snapshot.fragment);
|
||||
if (fragment !== null && graphWindowPreference !== fragment) {
|
||||
this.setValue(key, fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface';
|
||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
import { StateService } from './state.service';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
||||
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||
import { CacheService } from './cache.service';
|
||||
|
||||
const OFFLINE_RETRY_AFTER_MS = 1000;
|
||||
const OFFLINE_PING_CHECK_AFTER_MS = 10000;
|
||||
const OFFLINE_RETRY_AFTER_MS = 2000;
|
||||
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
||||
const EXPECT_PING_RESPONSE_AFTER_MS = 5000;
|
||||
|
||||
const initData = makeStateKey('/api/v1/init-data');
|
||||
@@ -28,6 +28,9 @@ export class WebsocketService {
|
||||
private isTrackingTx = false;
|
||||
private trackingTxId: string;
|
||||
private isTrackingMempoolBlock = false;
|
||||
private isTrackingRbf: 'all' | 'fullRbf' | false = false;
|
||||
private isTrackingRbfSummary = false;
|
||||
private isTrackingAddress: string | false = false;
|
||||
private trackingMempoolBlock: number;
|
||||
private latestGitCommit = '';
|
||||
private onlineCheckTimeout: number;
|
||||
@@ -39,6 +42,7 @@ export class WebsocketService {
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private transferState: TransferState,
|
||||
private cacheService: CacheService,
|
||||
) {
|
||||
if (!this.stateService.isBrowser) {
|
||||
// @ts-ignore
|
||||
@@ -107,10 +111,19 @@ export class WebsocketService {
|
||||
if (this.isTrackingMempoolBlock) {
|
||||
this.startTrackMempoolBlock(this.trackingMempoolBlock);
|
||||
}
|
||||
if (this.isTrackingRbf) {
|
||||
this.startTrackRbf(this.isTrackingRbf);
|
||||
}
|
||||
if (this.isTrackingRbfSummary) {
|
||||
this.startTrackRbfSummary();
|
||||
}
|
||||
if (this.isTrackingAddress) {
|
||||
this.startTrackAddress(this.isTrackingAddress);
|
||||
}
|
||||
this.stateService.connectionState$.next(2);
|
||||
}
|
||||
|
||||
if (this.stateService.connectionState$.value === 1) {
|
||||
if (this.stateService.connectionState$.value !== 2) {
|
||||
this.stateService.connectionState$.next(2);
|
||||
}
|
||||
|
||||
@@ -118,7 +131,7 @@ export class WebsocketService {
|
||||
},
|
||||
(err: Error) => {
|
||||
console.log(err);
|
||||
console.log(`WebSocket error, trying to reconnect in ${OFFLINE_RETRY_AFTER_MS} seconds`);
|
||||
console.log(`WebSocket error`);
|
||||
this.goOffline();
|
||||
});
|
||||
}
|
||||
@@ -148,10 +161,12 @@ export class WebsocketService {
|
||||
|
||||
startTrackAddress(address: string) {
|
||||
this.websocketSubject.next({ 'track-address': address });
|
||||
this.isTrackingAddress = address;
|
||||
}
|
||||
|
||||
stopTrackingAddress() {
|
||||
this.websocketSubject.next({ 'track-address': 'stop' });
|
||||
this.isTrackingAddress = false;
|
||||
}
|
||||
|
||||
startTrackAsset(asset: string) {
|
||||
@@ -173,6 +188,26 @@ export class WebsocketService {
|
||||
this.isTrackingMempoolBlock = false
|
||||
}
|
||||
|
||||
startTrackRbf(mode: 'all' | 'fullRbf') {
|
||||
this.websocketSubject.next({ 'track-rbf': mode });
|
||||
this.isTrackingRbf = mode;
|
||||
}
|
||||
|
||||
stopTrackRbf() {
|
||||
this.websocketSubject.next({ 'track-rbf': 'stop' });
|
||||
this.isTrackingRbf = false;
|
||||
}
|
||||
|
||||
startTrackRbfSummary() {
|
||||
this.websocketSubject.next({ 'track-rbf-summary': true });
|
||||
this.isTrackingRbfSummary = true;
|
||||
}
|
||||
|
||||
stopTrackRbfSummary() {
|
||||
this.websocketSubject.next({ 'track-rbf-summary': false });
|
||||
this.isTrackingRbfSummary = false;
|
||||
}
|
||||
|
||||
startTrackBisqMarket(market: string) {
|
||||
this.websocketSubject.next({ 'track-bisq-market': market });
|
||||
}
|
||||
@@ -197,11 +232,13 @@ export class WebsocketService {
|
||||
}
|
||||
|
||||
goOffline() {
|
||||
const retryDelay = OFFLINE_RETRY_AFTER_MS + (Math.random() * OFFLINE_RETRY_AFTER_MS);
|
||||
console.log(`trying to reconnect websocket in ${retryDelay} seconds`);
|
||||
this.goneOffline = true;
|
||||
this.stateService.connectionState$.next(0);
|
||||
window.setTimeout(() => {
|
||||
this.startSubscription(true);
|
||||
}, OFFLINE_RETRY_AFTER_MS);
|
||||
}, retryDelay);
|
||||
}
|
||||
|
||||
startOnlineCheck() {
|
||||
@@ -212,7 +249,7 @@ export class WebsocketService {
|
||||
this.websocketSubject.next({action: 'ping'});
|
||||
this.onlineCheckTimeoutTwo = window.setTimeout(() => {
|
||||
if (!this.goneOffline) {
|
||||
console.log('WebSocket response timeout, force closing, trying to reconnect in 10 seconds');
|
||||
console.log('WebSocket response timeout, force closing');
|
||||
this.websocketSubject.complete();
|
||||
this.subscription.unsubscribe();
|
||||
this.goOffline();
|
||||
@@ -222,15 +259,12 @@ export class WebsocketService {
|
||||
}
|
||||
|
||||
handleResponse(response: WebsocketResponse) {
|
||||
let reinitBlocks = false;
|
||||
|
||||
if (response.blocks && response.blocks.length) {
|
||||
const blocks = response.blocks;
|
||||
let maxHeight = 0;
|
||||
blocks.forEach((block: BlockExtended) => {
|
||||
if (block.height > this.stateService.latestBlockHeight) {
|
||||
maxHeight = Math.max(maxHeight, block.height);
|
||||
this.stateService.blocks$.next([block, false]);
|
||||
}
|
||||
});
|
||||
this.stateService.resetBlocks(blocks);
|
||||
const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight);
|
||||
this.stateService.updateChainTip(maxHeight);
|
||||
}
|
||||
|
||||
@@ -238,10 +272,17 @@ export class WebsocketService {
|
||||
this.stateService.mempoolTransactions$.next(response.tx);
|
||||
}
|
||||
|
||||
if (response['txPosition']) {
|
||||
this.stateService.mempoolTxPosition$.next(response['txPosition']);
|
||||
}
|
||||
|
||||
if (response.block) {
|
||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||
if (response.block.height === this.stateService.latestBlockHeight + 1) {
|
||||
this.stateService.updateChainTip(response.block.height);
|
||||
this.stateService.blocks$.next([response.block, !!response.txConfirmed]);
|
||||
this.stateService.addBlock(response.block);
|
||||
this.stateService.txConfirmed$.next([response.txConfirmed, response.block]);
|
||||
} else if (response.block.height > this.stateService.latestBlockHeight + 1) {
|
||||
reinitBlocks = true;
|
||||
}
|
||||
|
||||
if (response.txConfirmed) {
|
||||
@@ -257,6 +298,18 @@ export class WebsocketService {
|
||||
this.stateService.txReplaced$.next(response.rbfTransaction);
|
||||
}
|
||||
|
||||
if (response.rbfInfo) {
|
||||
this.stateService.txRbfInfo$.next(response.rbfInfo);
|
||||
}
|
||||
|
||||
if (response.rbfLatest) {
|
||||
this.stateService.rbfLatest$.next(response.rbfLatest);
|
||||
}
|
||||
|
||||
if (response.rbfLatestSummary) {
|
||||
this.stateService.rbfLatestSummary$.next(response.rbfLatestSummary);
|
||||
}
|
||||
|
||||
if (response.txReplaced) {
|
||||
this.stateService.txReplaced$.next(response.txReplaced);
|
||||
}
|
||||
@@ -327,6 +380,11 @@ export class WebsocketService {
|
||||
|
||||
if (response.loadingIndicators) {
|
||||
this.stateService.loadingIndicators$.next(response.loadingIndicators);
|
||||
if (response.loadingIndicators.mempool != null && response.loadingIndicators.mempool < 100) {
|
||||
this.stateService.isLoadingMempool$.next(true);
|
||||
} else {
|
||||
this.stateService.isLoadingMempool$.next(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.mempoolInfo) {
|
||||
@@ -344,5 +402,9 @@ export class WebsocketService {
|
||||
if (response['git-commit']) {
|
||||
this.stateService.backendInfo$.next(response['git-commit']);
|
||||
}
|
||||
|
||||
if (reinitBlocks) {
|
||||
this.websocketSubject.next({'refresh-blocks': true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user